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?
Main in #Includes
Moderator: English Moderator
- Awpteamoose
- Posts: 76
- Joined: 12 Jul 2012, 20:27
Re: Main in #Includes
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.
Re: Main in #Includes
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.
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.
- Gugli
- 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
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.
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.
--
(>~_~)> â•╦╠╔╦╗ <(~_~<)
(>~_~)> â•╦╠╔╦╗ <(~_~<)
Re: Main in #Includes
I dont seem to be able to access that forum.
Thank you for the tip with BattleWave mode, ill take a look at it.
Thank you for the tip with BattleWave mode, ill take a look at it.
Re: Main in #Includes
You also have Gugli's answer about #Extends in the Labels section of that post : http://forum.maniaplanet.com/viewtopic. ... 99#p111399
Developer/maintainer of TM² Dedimania records system - TM² Fast4 server script (download , french topic)
Re: Main in #Includes
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:
InstagibDM.Script.txt:
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
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();
}
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;
}
***
The script just doesnt compile: "syntax error, unexpected", pointing to the end of InstagibDM.Script.txt
Re: Main in #Includes
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:
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.
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() {}
Yes, ManiaScript can be strange sometimes ...
Oh, and multiple extends should work, we use it in the next version of Battle.
Re: Main in #Includes
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.
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.
Who is online
Users browsing this forum: No registered users and 1 guest