Main in #Includes

You can talk about ManiaScript for ManiaPlanet here

Moderator: English Moderator

Post Reply
Deaod
Posts: 19
Joined: 17 Jun 2012, 12:09

Main in #Includes

Post by Deaod »

Basically what i wanted to do is create a basic script that has a main function and only calls certain callbacks (OnHit, OnFrame, OnServerStart, OnServerEnd, OnMapStart, OnMapEnd, OnRoundStart, OnRoundEnd, ...). The basic script would then be included in a bigger script that implements these callbacks thus bringing the mode to life.

First i tried using an #Include at the bottom of the bigger script. That doesnt work. Apparently directives can only be used at the top (the basic syntax guide mentions that almost in passing, and i read over that initially). So i place the #Include at the top. Then the game informs me that a main should not be found in an included file.

Any way to achieve my goal?
User avatar
Awpteamoose
Posts: 76
Joined: 12 Jul 2012, 20:27

Re: Main in #Includes

Post by Awpteamoose »

Make your own main, just write main() in the bigger script and call MyLib::_main() as the first line, in the inclusion script make _main() and put everyithing inside there.
Deaod
Posts: 19
Joined: 17 Jun 2012, 12:09

Re: Main in #Includes

Post by Deaod »

That in my opinion defeats the point of abstracting away the whole event nonsense. Best practices (especially for bigger projects) demand separation of concerns and reduction of duplicate code.

Im trying to refactor my code and abstract away the main method so the code essentially consists of event callbacks, so merging to main function back into the actual script is not an option if i want to make progress. It might very well be that i cant do what i want with the current language, in which case id request a change to the language and in the meantime would turn to other things to try out.
User avatar
Gugli
Nadeo
Nadeo
Posts: 536
Joined: 14 Jun 2010, 17:35
Location: Mon PC est à Paris, mais mon coeur rode dans les forêts de Lozère

Re: Main in #Includes

Post by Gugli »

You should probably use #Extends instead of #Include.

Some doc can be found here : http://forum.maniaplanet.com/viewtopic. ... 73&t=12245.

Also you may want to look at the BattleWaves mode, which extends Battle.

Hope it helps.
--
(>~_~)> ═╦═ ╔╦╗ <(~_~<)
Deaod
Posts: 19
Joined: 17 Jun 2012, 12:09

Re: Main in #Includes

Post by Deaod »

I dont seem to be able to access that forum.

Thank you for the tip with BattleWave mode, ill take a look at it.
User avatar
Slig
Posts: 637
Joined: 15 Jun 2010, 11:52
Location: TraXicoLand

Re: Main in #Includes

Post by Slig »

You also have Gugli's answer about #Extends in the Labels section of that post : http://forum.maniaplanet.com/viewtopic. ... 99#p111399
Deaod
Posts: 19
Joined: 17 Jun 2012, 12:09

Re: Main in #Includes

Post by Deaod »

Okay, so i have finally broken down and used #Extends together with labels. Turns out function overriding doesnt exist/work, multiple #Extends also dont work, directives cant be used after the first normal code statement, and theres weird separation of code spaces. The important thing is labels seem to be the only way to do what i want to do.

Except they dont work either.

Ill just dump lots of code so you can actually see what im trying to get at.

ModeBase.Script.txt:

Code: Select all

#RequireContext CSmMode

***OnServerStartup***
***
    
***

***OnServerShutdown***
***
    
***

***OnMatchStart***
***
    
***

***OnMatchEnd***
***
    
***

***IsMatchOver***
***
    return False;
***

***OnRoundStart***
***
    
***

***OnRoundEnd***
***
    
***

***IsRoundOver***
***
    return False;
***

***OnUnknownEvent***
***
    PassOn(Event);
***

***OnShootEvent***
***
    PassOn(Event);
***

***OnHitEvent***
***
    PassOn(Event);
***

***OnArmorEmptyEvent***
***
    PassOn(Event);
***

***OnCaptureEvent***
***
    PassOn(Event);
***

***OnPlayerRequestRespawnEvent***
***
    PassOn(Event);
***

***OnServerTick***
***
    
***

// Server events
Void OnServerStartupF() {
---OnServerStartup---
}
Void OnServerShutdownF() {
---OnServerShutdown---
}

// Match events
Void OnMatchStartF() {
---OnMatchStart---
}
Void OnMatchEndF() {
---OnMatchEnd---
}
Boolean IsMatchOverF() {
---IsMatchOver---
}

// Round events
Void OnRoundStartF() {
---OnRoundStart---
}
Void OnRoundEndF() {
---OnRoundEnd---
}
Boolean IsRoundOverF() {
---IsRoundOver---
}

// Gameplay events
Void OnUnknownEventF(CSmModeEvent Event) {
---OnUnknownEvent---
}
Void OnShootEventF(CSmModeEvent Event) {
---OnShootEvent---
}
Void OnHitEventF(CSmModeEvent Event) {
---OnHitEvent---
}
Void OnArmorEmptyEventF(CSmModeEvent Event) {
---OnArmorEmptyEvent---
}
Void OnCaptureEventF(CSmModeEvent Event) {
---OnCaptureEvent---
}
Void OnPlayerRequestRespawnEventF(CSmModeEvent Event) {
---OnPlayerRequestRespawnEvent---
}

// General Events
Void OnServerTickF() {
---OnServerTick---
}

/**
 * Process all events triggered by the game
 */
Void ProcessEvents() {
    foreach (Event in PendingEvents) {
        switch(Event.Type) {
            case CSmModeEvent::EType::Unknown: {
                OnUnknownEventF(Event);
            }
            
            case CSmModeEvent::EType::OnShoot: {
                OnShootEventF(Event);
            }
            
            case CSmModeEvent::EType::OnHit:{
                OnHitEventF(Event);
            }
            
            case CSmModeEvent::EType::OnArmorEmpty: {
                OnArmorEmptyEventF(Event);
            }
            
            case CSmModeEvent::EType::OnCapture: {
                OnCaptureEventF(Event);
            }
            
            case CSmModeEvent::EType::OnPlayerRequestRespawn: {
                OnPlayerRequestRespawnEventF(Event);
            }
            
            default: {
                PassOn(Event);
            }
        }
    }
}

/**
 * Main
 */
main() {
    OnServerStartup();
    while (!ServerShutdownRequested) {
        OnMatchStartF();
        
        while (!MatchEndRequested && !ServerShutdownRequested) {
            OnRoundStartF();
            
            while(!MatchEndRequested && !ServerShutdownRequested) {
                yield;
                ProcessEvents();
                OnServerTickF();
                if (IsRoundOverF()) break;
            }
            
            OnRoundEndF();
            if (IsMatchOverF()) break;
        }
        
        OnMatchEndF();
    }
    OnServerShutdownF();
}
InstagibDM.Script.txt:

Code: Select all

#Extends "Modes/ShootMania/ModeBase.Script.txt"

#Include "MathLib" as MathLib
#Include "TextLib" as TextLib
#Include "Libs/Nadeo/Mode.Script.txt" as Mode
#Include "Libs/Nadeo/ShootMania/SM.Script.txt" as SM
#Include "Libs/Nadeo/ShootMania/Score.Script.txt" as Score

#Const	CompatibleMapTypes  "MeleeArena"
#Const	Version             "2012-07-22"

/* -------------------------------------- */
// Settings
/* -------------------------------------- */
#Setting    TIME_LIMIT	    600 as _("Time limit (seconds)")	// Time limit on a map // in seconds
#Setting    POINT_LIMIT	    25	as _("Point limit")	// Point limit on a map
#Setting    KILL_REWARD     1 as _("Kill Reward") // players get awarded this many points when killing another player
#Setting    SUICIDE_PENALTY 0 as _("Suicide Penalty") // players get their score reduced by this amount when suiciding
#Setting    RELOAD_SPEED    1.7 as _("Reload Speed") // reload factor // default 1.7, minimum 0.0, maximum 10.0 
                                                     // 2.0 means double reload speed, ie. you can fire twice as fast
#Setting    MUTATE_SPAWNS   True as _("Randomize Spawns") // if false, will use the maps default order of spawn blocks // definitely turn on for 1on1

#Const  UI_TICK_INTERVAL	500	// Time interval between UI update // in milliseconds

/* -------------------------------------- */
// Globales variables
/* -------------------------------------- */
declare CSmBlockSpawn[]	BlockSpawnList;		// List of all the BlockSpawns of the map

declare CUILayer LayerSpawnScreen;
declare CUILayer LayerScoresTable;

declare Integer LastUITick;

/* -------------------------------------- */
// Functions
/* -------------------------------------- */

/** 
 * Fill BlockSpawnList with all the BlockSpanws of the map
 */
Void FillSpawnsList() {
	foreach (BlockSpawn in BlockSpawns) { BlockSpawnList.add(BlockSpawn); }
}

/**
 * Spawn a player
 *
 * @param	Player		The player to spawn
 */
Void PlayerSpawn(CSmPlayer Player) {
	SM::SpawnPlayer(Player, 0, BlockSpawnList[0]);
	SetPlayerWeapon(Player, CSmMode::EWeapon::Laser, False);
	Player.AmmoGain = RELOAD_SPEED;
	Player.ArmorMax = 100;
	
	// move spawn to the end of the list
	declare i=0;
	while (i < BlockSpawnList.count-1) { // assuming count of 10, loop 9 times.
	    declare tmp=BlockSpawnList[i+1].Id;
	    BlockSpawnList[i+1]=BlockSpawnList[i];
	    BlockSpawnList[i]=BlockSpawns[tmp];
		i=i+1;
	}
}

/** 
 * Mutate (ie. shuffle) the list of spawns
 */
Void MutateSpawnsList() {
    // TODO maybe mutate the list every once in a while
    declare i=0;
    while(i<BlockSpawnList.count) { // this should be enough, you can use arbitrary values, should the need arise
        // find a pair (a,b) of valid indices to swap
        declare a=MathLib::Rand(0, BlockSpawnList.count-1);
        declare b=MathLib::Rand(0, BlockSpawnList.count-1);
        
        // now swap them
        declare tmp=BlockSpawnList[b].Id;
        BlockSpawnList[b]=BlockSpawnList[a];
        BlockSpawnList[a]=BlockSpawns[tmp];
        
        i=i+1;
    }
}

/** 
 * Initialize all spawns on the map
 */
Void InitSpawns() {
    BlockSpawnList.clear();
    
	FillSpawnsList();
	
	if (MUTATE_SPAWNS) MutateSpawnsList();
}

/**
 * Update the infos UI of a player
 *
 * @param	Player		The player to update
 *
 * @return		The ManiaLink string
 */
Text UpdateLayerInfos(CSmPlayer Player) {
	if (Player.Score == Null) return "";
	
	return """
		<frame posn="150 -88">
			<label posn="0 1" halign="left" valign="bottom" text="/{{{ Scores.count }}}"/>
			<label posn="0 0" halign="right" valign="bottom" style="TextRaceChrono" text="{{{ Scores.keyof(Player.Score) + 1 }}}"/>
		</frame>
	""";
}

/** 
 * Get the help manialink string.
 *
 * @return		The manialink string
 */
Text UpdateLayerSpawnScreen() {
	declare Text ML;
	
	declare KillRewardText=TextLib::ToText(KILL_REWARD)^" ";
    if (KILL_REWARD==1) KillRewardText=KillRewardText^_("point");
    else KillRewardText=KillRewardText^_("points");
        
    declare SuicidePenaltyText=TextLib::ToText(SUICIDE_PENALTY)^" ";
    if (SUICIDE_PENALTY==1) SuicidePenaltyText=SuicidePenaltyText^_("point");
    else SuicidePenaltyText=SuicidePenaltyText^_("points");
	
	declare RulesText = TextLib::Compose(_("InstaGib DeathMatch\n\nFree-for-all where every hit kills the victim. Every kill gives you %1. Suicides reduce your score by %2.\nThe first player to reach %3 points wins.\n\n\nCredits: Triseaux (original script)"),  KillRewardText, SuicidePenaltyText, TextLib::ToText(POINT_LIMIT));
	declare RulesTip  = _("Press F1 to show rules");
	
	ML = """
		<script><!--
			main () {
				declare FrameRules	<=> Page.GetFirstChild("FrameRules");
				declare FrameShow	<=> Page.GetFirstChild("FrameShow");
				declare ShowRules = False;
				
				while(True) {
					if (ShowRules) {
						FrameRules.Show();
						FrameShow.Hide();
					} else {
						FrameRules.Hide();
						FrameShow.Show();
					}
					
					yield;
					
					// process events.
					foreach (Event in PendingEvents) {
						switch (Event.Type) {
							case CMlEvent::Type::MouseClick :
							{
								if (Event.ControlId == "FrameRules") ShowRules = !ShowRules;
							}
							
							case CMlEvent::Type::KeyPress:
							{
								if (Event.CharPressed == "2424832") ShowRules = !ShowRules;	// F1
							}
						}
					}
				}
			}
		--></script>
		<frame posn="0 -70 0" id="FrameShow">
			<quad posn="0 0 10" sizen="140 20" halign="center" valign="center" style="Bgs1InRace" substyle="BgTitle3_5" />
			<label posn="0 0 11" scale="2" halign="center" valign="center" style="TextTitle3" text="{{{ RulesTip }}}" />
		</frame>
		<frame posn="0 50 1" id="FrameRules">
			<frame posn="0 0 5">
				<quad posn="0 0 10" sizen="120 20" halign="center" valign="center" style="Bgs1InRace" substyle="BgTitle3_5" />
				<label posn="0 0 11" scale="2" halign="center" valign="center" style="TextTitle3" text="$fffInstaGib DeathMatch" />
			</frame>
			<frame posn="0 -10 5">
				<quad posn="0 0 10" sizen="300 120" halign="center" bgcolor="222c" />
				<label posn="-145 -5 11" sizen="145 5" scale="2" autonewline="1" style="TextCardSmallScores2" text="{{{ RulesText }}}" />
			</frame>
		</frame>
	""";
	
	return ML;
}

/**
 * Play a short intro
 */
Void PlayIntro() {
    UIManager.UIAll.UISequence = CUIConfig::EUISequence::Intro;
	UIManager.UIAll.BigMessageSound = CUIConfig::EUISound::StartRound;
	UIManager.UIAll.BigMessageSoundVariant = 0;
	UIManager.UIAll.BigMessage = _("New match");
	wait(UIManager.UIAll.UISequenceIsCompleted);
	UIManager.UIAll.BigMessage = "";
}

/**
 * Set up the Help screen
 */
Void AddSpawnScreen() {
    LayerSpawnScreen <=> UIManager.UILayerCreate();
    LayerSpawnScreen.Type = CUILayer::EUILayerType::ScreenIn3d;
    LayerSpawnScreen.ManialinkPage = UpdateLayerSpawnScreen();
	UIManager.UIAll.UILayers.add(LayerSpawnScreen);
}

/**
 * Set up the scoreboard
 */
Void AddScoresTable() {
    LayerScoresTable <=> UIManager.UILayerCreate();
	LayerScoresTable.Type = CUILayer::EUILayerType::ScoresTable;
    LayerScoresTable.ManialinkPage = """
		<frame posn="0 -47">
			<quad posn="0 0 1" sizen="40 8" style="Bgs1InRace" halign="center" substyle="BgList"/>
			<label posn="0 -2 2" sizen="40 8" halign="center" text="{{{_("Point limit")}}}: {{{ POINT_LIMIT }}}" />
		</frame>
	""";
	UIManager.UIAll.UILayers.add(LayerScoresTable);
}

/**
 * Set up the UI: help screen and scoreboard
 */
Void SetupUI() {
    UIManager.ResetAll();
	SM::SetupDefaultVisibility();
	
	AddSpawnScreen();
	AddScoresTable();
	
	LastUITick = 0;
}

/**
 * Respawn all players that want to play and are currently dead
 */
Void RespawnPlayers() {
    foreach (Player in Players) {
		if (Player.SpawnStatus == CSmPlayer::ESpawnStatus::NotSpawned && !Player.RequestsSpectate) {
			PlayerSpawn(Player);
		}
	}
}

/**
 * Updating the UI
 */
Void UpdateUI() {
    declare UsedLayers = Ident[];
	
	// Add layer and create it if necessary
	foreach (Player in Players) {
		declare UI <=> UIManager.GetUI(Player);
		if (UI == Null) continue;
		
		declare CUILayer LayerInfos;
		if (UI.UILayers.count != 1) {
			LayerInfos <=> UIManager.UILayerCreate();
			UI.UILayers.add(LayerInfos);
		} else {
			LayerInfos <=> UI.UILayers[0];
		}
		UsedLayers.add(LayerInfos.Id);
		LayerInfos.ManialinkPage = UpdateLayerInfos(Player);
	}
	// Remove layers
	declare LayersToRemove = Ident[];
	foreach (Layer in UIManager.UIAll.UILayers) { UsedLayers.add(Layer.Id); }
	foreach (Layer in UIManager.UILayers) {
		if (!UsedLayers.exists(Layer.Id)) {
			LayersToRemove.add(Layer.Id);
		}
	}
	foreach (LayerId in LayersToRemove) {
		UIManager.UILayerDestroy(UIManager.UILayers[LayerId]);
	}
}

// Server events
***OnServerStartup***
***
    UseClans = False;
***

// Match events
***OnMatchStart***
***
    Mode::LoadMap();
    Score::MatchBegin();
	Score::RoundBegin();
    
	MatchEndRequested = False;
	
    InitSpawns();
	SetupUI();
	PlayIntro();
	
	StartTime = Now;
    EndTime = StartTime + (TIME_LIMIT * 1000);
	
	UIManager.UIAll.UISequence = CUIConfig::EUISequence::Playing;
***

***OnMatchEnd***
***
    Score::RoundEnd();
	Score::MatchEnd(True);
	
	/* -------------------------------------- */
	// End match sequence
	declare CUser Winner <=> Null;
	declare MaxPoints = 0;
	foreach (Score in Scores) {
		if (Score.Points > MaxPoints) {
			MaxPoints = Score.Points;
			Winner <=> Score.User;
		} else if (Score.Points == MaxPoints) {
			Winner <=> Null;
		}
	}
	foreach (Player in Players) {
		if (Player.User != Winner) UnspawnPlayer(Player);
	}
	
	UIManager.UIAll.BigMessageSound = CUIConfig::EUISound::EndRound;
	UIManager.UIAll.BigMessageSoundVariant = 0;
	if (Winner != Null) {
		UIManager.UIAll.BigMessage = TextLib::Compose(_("$<%1$> wins the match!"), Winner.Name);
	} else {
		UIManager.UIAll.BigMessage = _("|Match|Draw");
	}
	sleep(2000);
	UIManager.UIAll.UISequence = CUIConfig::EUISequence::EndRound;
	UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::ForcedVisible;
	sleep(5000);
	
	UIManager.UIAll.UISequence = CUIConfig::EUISequence::Podium;
	wait(UIManager.UIAll.UISequenceIsCompleted);
	
	UIManager.UIAll.ScoreTableVisibility = CUIConfig::EVisibility::Normal;
	UIManager.UIAll.BigMessage = "";
	
	UIManager.UILayerDestroy(LayerSpawnScreen);
	UIManager.UILayerDestroy(LayerScoresTable);
	
	Mode::UnloadMap();
***

Boolean MatchEndTriggered() {
    if (Now > StartTime + (TIME_LIMIT * 1000)) {
		return True;
	}
	foreach (Player in Players) {
		if (Player.Score != Null && POINT_LIMIT > 0 && Player.Score.RoundPoints >= POINT_LIMIT) {
		    return True;
		}
	}
	return False;
}

***IsMatchOver***
***
	return MatchEndTriggered();
***

***IsRoundOver***
***
    return MatchEndTriggered();
***

// Gameplay events

***OnHitEvent***
***
    if (Event.Shooter == Event.Victim) {
		Discard(Event);
	} else {
		Score::AddPoints(Event.Shooter, KILL_REWARD);
		Event.ShooterPoints = KILL_REWARD;
		// Play sound and notice if someone is close to win
		if (Event.Shooter != Null && Event.Shooter.Score != Null && POINT_LIMIT > 0) {
			declare Gap = POINT_LIMIT - Event.Shooter.Score.RoundPoints;
			if (Gap <= 3 && Gap > 0) {
				declare Variant = 3 - Gap;
				declare Msg = "";
				if (Gap > 1)
					Msg = TextLib::Compose(_("$<%1$> is %2 points from victory!"), Event.Shooter.Name, TextLib::ToText(Gap));
				else 
					Msg = TextLib::Compose(_("$<%1$> is 1 point from victory!"), Event.Shooter.Name);
				UIManager.UIAll.SendNotice(
					Msg, 
					CUIConfig::ENoticeLevel::PlayerInfo, 
					Null,
					CUIConfig::EAvatarVariant::Default, 
					CUIConfig::EUISound::TieBreakPoint,
					Variant
				);
			} else if (Gap <= 0) {
				UIManager.UIAll.SendNotice(
					TextLib::Compose(_("$<%1$> gets the final hit!"), Event.Shooter.Name), 
					CUIConfig::ENoticeLevel::PlayerInfo, 
					Null,
					CUIConfig::EAvatarVariant::Default, 
					CUIConfig::EUISound::VictoryPoint,
					0
				);
				
			}
		}
		PassOn(Event);
	}
***

***OnArmorEmptyEvent***
***
    if (Event.Shooter == Event.Victim || Event.Shooter == Null) {
        Score::RemovePoints(Event.Victim, SUICIDE_PENALTY);
	}
	PassOn(Event);
***

***OnPlayerRequestRespawnEvent***
***
    Score::RemovePoints(Event.Player, SUICIDE_PENALTY);
    PassOn(Event);
***

// General Events
***OnServerTick***
***
    RespawnPlayers();
	
	// only update the UI every once in a while
	if (LastUITick + UI_TICK_INTERVAL < Now) {
		UpdateUI();
		LastUITick = Now;
	}
***
I start a local server with InstagibDM.Script.txt.

The script just doesnt compile: "syntax error, unexpected", pointing to the end of InstagibDM.Script.txt
User avatar
Eole
Nadeo
Nadeo
Posts: 1265
Joined: 26 Apr 2011, 21:08

Re: Main in #Includes

Post by Eole »

I'm currently working on something really similar to what you're doing (to tell the truth we even have the same name "ModeBase" ^^).
In the InstagibDM.Script.txt, all the ***LabelName*** must be placed before a function. If you don't want to reorder all the content of the file you could just add something like that at the end of the script:

Code: Select all

Void Tmp() {}
The error should go away with that. And you'll have to fill all the labels in the ModeBase script after that.
Yes, ManiaScript can be strange sometimes ...

Oh, and multiple extends should work, we use it in the next version of Battle.
Contribute to the ManiaPlanet documentation on GitHub
A question about ManiaScript? Ask it here!
Deaod
Posts: 19
Joined: 17 Jun 2012, 12:09

Re: Main in #Includes

Post by Deaod »

So Ive tried to make it work. I really have. But it just doesnt work. Leaving aside the fact that empty label replacements dont work (which would be handy), you cant call functions defined in the same file label replacements are defined in from those label replacements.

Ill just make a template for myself and be done with it. If you decide in the future to simply abandon the whole label stuff (who really thinks this is a good idea anyway?) and just allow extending scripts to overwrite functions, ill gladly try again.

I could also try writing a preprocessor.
Post Reply

Return to “ManiaScript”

Who is online

Users browsing this forum: No registered users and 1 guest