// This script contains useful game specific utility functions. /////////////////////////////////////////////////////////////////////////////// // Set up some core utility module with game specific stuff. Util.PlayerClassTable = { CLASS.SOLDIER, CLASS.MEDIC, CLASS.ENGINEER, CLASS.FIELDOPS, CLASS.COVERTOPS, }; if (GetModName() == "etblight") { Util.PlayerClassTable[5] = CLASS.SCIENTIST; Util.PlayerClassTable[6] = CLASS.SUPER_SOLDIER; } Util.SniperClass = CLASS.COVERTOPS; // offset for converted waypoint goals Util.WaypointGoalOffset = Vector3(0,0,-24); Util.AllTeams = (1 << TEAM.AXIS) | (1 << TEAM.ALLIES); Util.PrimaryRouteGoalQuery = "BUILD_.*\nPLANT_.*\nFLAG_.*\nCHECKPOINT_.*"; // debug string color Util.DebugColorString = "^b"; /////////////////////////////////////////////////////////////////////////////// // these values are for makegm.gm Util.FlagCarrier = "CLASS.ENGINEER"; Util.Sniper = "CLASS.COVERTOPS"; Util.HaveTeam = { false, true, true, false, false }; Util.TeamNames = { { "", "", "" }, { "AXIS", "Axis", "axis" }, { "ALLIES", "Allies", "allies" }, }; /////////////////////////////////////////////////////////////////////////////// // these values are for testmap.gm Util.NoTimeLimit = "timelimit 0"; Util.TimeLimit1 = "timelimit 1"; Util.BotNames = { { "", "", "", "", "", "", }, { "", "axis_soldier", "axis_medic", "axis_engineer", "axis_fieldops", "axis_covertops", }, { "", "allies_soldier", "allies_medic", "allies_engineer", "allies_fieldops", "allies_covertops", }, }; /////////////////////////////////////////////////////////////////////////////// // store time at start of map since GetTime() is server uptime. set in PostMapLoad member MapStartTime = 0.0; member GetTimeElapsed = function() { t = GetTime(); return t - ETUtil.MapStartTime; }; /////////////////////////////////////////////////////////////////////////////// // todo: make generic with a Util.GenericGoals table member ClearMainGoals = function() { Util.DisableGoal({ "BUILD.*", "PLANT.*", "MOUNTMG42.*", "MOVER.*", "CHECKPOINT.*", "FLAG.*", "AMMOCAB.*", "HEALTHCAB.*" }); }; /////////////////////////////////////////////////////////////////////////////// // todo: make generic with a Util.SecondaryGoals table member ClearSecondaryGoals = function() { Util.DisableGoal({ "HEALTH.*", "AMMO.*", "REPAIRMG42.*", "PLANTMINE.*", "MOBILEMG42.*", "ARTILLERY.*", }); }; /////////////////////////////////////////////////////////////////////////////// // member CheckVehiclePath = function( _params ) { if (this.CheckVehiclePathFlag) { this.CheckVehiclePathFlag = null; return; } if ( !Util.CheatsEnabled() ) { return; } goalname = _params[ 0 ]; goal = Util.GetGoal( goalname, "ETUtil.CheckVehiclePath" ); if ( !goal ) { return; } if (goal.GoalType != "BUILD") { print( "CheckVehiclePath: goal type is not BUILD" ); return; } goalname2 = goal.Vehicle; if (!goalname2) { print( "CheckVehiclePath: Goal", goalname, "has no vehicle property (MOVER goal)" ); return; } goal2 = Util.GetGoal( goalname2, "ETUtil.CheckVehiclePath" ); if ( !goal2 ) { return; } if ( goal.IsAvailable( TEAM.ALLIES ) ) { print("Allies objective"); team = TEAM.ALLIES; botname = "allies_engineer"; } else if ( goal.IsAvailable( TEAM.AXIS ) ) { print("Axis objective"); team = TEAM.AXIS; botname = "axis_engineer"; } else { print( "CheckVehiclePath: Goal:", goalname, "not available" ); return; } this.CheckVehiclePathFlag = true; AddBot( team, CLASS.ENGINEER, botname ); entity = goal2.GetEntity(); sleeptime = _params[ 1 ]; if ( !sleeptime ) { sleeptime = 2; } print( "Started checking vehicle path for:", goalname2 ); while ( this.CheckVehiclePathFlag ) { EntityKill( entity ); yield(); SetAvailableMapGoals( team, false, ".*" ); SetAvailableMapGoals( team, true, { goalname, goalname2 } ); while ( Util.GetEntHealth(entity) <= 0 && this.CheckVehiclePathFlag ) { yield(); } if ( !this.CheckVehiclePathFlag ) { break; } sleep( sleeptime ); } print( "Finished checking vehicle path for:", goalname2 ); }; /////////////////////////////////////////////////////////////////////////////// // member BuildVehiclePath = function( _params ) { if (this.BuildVehiclePathFlag) { this.BuildVehiclePathFlag = null; return; } if ( !Util.CheatsEnabled() ) { return; } goalname = _params[ 0 ]; goal = Util.GetGoal( goalname, "ETUtil.BuildVehiclePath" ); if ( !goal ) { return; } if (goal.GoalType != "MOVER") { print( "BuildVehiclePath: goal type is not MOVER" ); return; } entity = goal.GetEntity(); this.BuildVehiclePathFlag = true; sleeptime = _params[ 1 ]; if ( !sleeptime ) { sleeptime = 2; } print( "Started build vehicle path for:", goalname ); while ( this.BuildVehiclePathFlag ) { EntityKill( entity ); while ( Util.GetEntHealth(entity) <= 0 && this.BuildVehiclePathFlag ) { yield(); } if ( !this.BuildVehiclePathFlag ) { break; } sleep( sleeptime ); } print( "Finished buidling vehicle path for:", goalname ); }; /////////////////////////////////////////////////////////////////////////////// // member CountClass = function( team, class ) { count = 0; foreach ( bot in BotTable ) { if ( bot.GetTeam() == team && bot.GetClass() == class ) { count += 1; } } return count; }; /////////////////////////////////////////////////////////////////////////////// // member NoSnipeWeapons = { WEAPON.STEN, WEAPON.FG42, }; /////////////////////////////////////////////////////////////////////////////// // member NoSnipe = function( bot ) { if ( bot.GetClass() != CLASS.COVERTOPS ) { return; } if (!Map.DontSelectWeapons) { Util.MapDeprecateMessage( "ETUtil.NoSnipe", "SetAvailableMapGoals" ); return; } if ( Util.CanBotSnipe(bot) ) { bot.ChangePrimaryWeapon( Util.GetRandomTableValue(this.NoSnipeWeapons) ); } }; /////////////////////////////////////////////////////////////////////////////// // member StopSniping = function(team) { if (!Map.DontSelectWeapons) { Util.MapDeprecateMessage( "ETUtil.StopSniping", "SetAvailableMapGoals" ); return; } foreach ( bot in BotTable ) { if ( team && bot.GetTeam() != team ) { continue; } this.NoSnipe(bot); } }; /////////////////////////////////////////////////////////////////////////////// // member SetPrimaryGoals = function(priority) { SetGoalPriority( "CAPPOINT_.*", priority ); SetGoalPriority( "PLANT_.*", priority - 0.08f ); SetGoalPriority( "CHECKPOINT_.*", priority - 0.1f ); SetGoalPriority( "FLAG_.*", priority - 0.15f, 0, 0, true ); SetGoalPriority( "FLAGRETURN_.*", priority - 0.05f, 0, 0, true ); }; /////////////////////////////////////////////////////////////////////////////// // parameter isFog is ignored, goal_zooming sets MaxViewDistance member SelectWeapon = function( bot, w, isFog ) { if (!Map.DontSelectWeapons) { // goal_selectweapons changes weapon every spawn Util.MapDeprecateMessage( "ETUtil.SelectWeapon", "WeaponTable.SetWeaponAvailability" ); return; } tbl = WeaponTable.GetPrimaryWeapons( bot.GetTeam(), bot.GetClass() ); if (!tbl || !IsNull(tbl[w])) { bot.ChangePrimaryWeapon( w ); } }; /////////////////////////////////////////////////////////////////////////////// // member SwitchWeapon = function( w, team ) { if (!Map.DontSelectWeapons) { Util.MapDeprecateMessage( "ETUtil.SwitchWeapon", "WeaponTable.SetWeaponAvailability" ); return; } foreach ( bot in BotTable ) { if ( team && bot.GetTeam() != team ) { continue; } this.SelectWeapon( bot, w ); } }; /////////////////////////////////////////////////////////////////////////////// // member SetCabinets = function() { SetGoalPriority( "HEALTHCAB.*", 0.0, 0, CLASS.MEDIC); SetGoalPriority( "AMMOCAB.*", 0.0, 0, CLASS.FIELDOPS); }; /////////////////////////////////////////////////////////////////////////////// // member SetDualObjective = function( bot ) { team = bot.GetTeam(); if ( !Map.Defend ) { Map.Defend = table(); } // counters for number of users if ( !Map.Defend.AlliesDefenders ) { Map.Defend.AlliesDefenders = 0; } if ( !Map.Defend.AxisDefenders ) { Map.Defend.AxisDefenders = 0; } // some default values if ( !Map.Defend.MaxBots ) { Map.Defend.MaxBots = 4; } if ( team == TEAM.ALLIES ) { if (IsNull(this.numallies)) { this.numallies = 0; } this.numallies += 1; if ( Map.Defend.AlliesDefenders < Map.Defend.MaxBots || Map.Defend.AlliesDefenders < Map.Defend.MaxAlliesDefenders ) { if ( this.numallies % 2 == 0 ) { bot.SetRoles(ROLE.DEFENDER); //first bot defending will switch to crucial class if ( Map.Defend.AlliesDefenders == 0 && Map.Defend.CrucialClass ) { bot.ChangeClass(Map.Defend.CrucialClass); } if ( Map.Defend.AlliesSpawnPt ) { bot.ChangeSpawnPoint( Map.Defend.AlliesSpawnPt ); } Map.Defend.AlliesDefenders += 1; } } } if ( team == TEAM.AXIS ) { if (IsNull(this.numaxis)) { this.numaxis = 0; } this.numaxis += 1; if ( Map.Defend.AlliesDefenders < Map.Defend.MaxBots || Map.Defend.AlliesDefenders < Map.Defend.MaxAxisDefenders ) { if ( this.numaxis % 2 == 0 ) { bot.SetRoles(ROLE.DEFENDER); //first bot defending will switch to crucial class if ( Map.Defend.AxisDefenders == 0 && Map.Defend.CrucialClass ) { bot.ChangeClass(Map.Defend.CrucialClass); } if ( Map.Defend.AxisSpawnPt ) { bot.ChangeSpawnPoint( Map.Defend.AxisSpawnPt ); } Map.Defend.AxisDefenders += 1; } } } }; /////////////////////////////////////////////////////////////////////////////// // member TraceArty = function(usePlayer) { radius = 200; playerEnt = GetLocalEntity(); playerPos = GetEntPosition(playerEnt); foundGoal = null; sourceOffset = Vector3(0,0,60); // 60 is what the goal uses if ( !usePlayer ) { dist = radius; foreach ( mapgoal in Util.GoalTable("CALLART.*") ) { evalDist = DistanceBetween(mapgoal, playerPos); if ( evalDist < dist ) { dist = evalDist; foundGoal = mapgoal; } } } if (foundGoal || usePlayer) { if ( !usePlayer ) { print("ETUtil.TraceArty: Selected", foundGoal.GetName()); sourcePos = foundGoal.GetPosition(); } else { print("ETUtil.TraceArty: Tracing from", GetEntName(playerEnt), "^2"); sourcePos = playerPos - Vector3(0,0,24); } targets = Util.GoalTable("ARTILLERY.*"); if ( tableCount(targets) > 0 ) { foreach ( targetgoal in targets ) { targetPos = targetgoal.GetPosition(); targetName = targetgoal.GetName(); tr = TraceLine(sourcePos + sourceOffset, targetPos, null, TRACE.SHOT, playerEnt, false); if ( tr.fraction < 1 ) { print("ETUtil.TraceArty: ^1No line of sight to", targetName, "^2"); DrawDebugLine(sourcePos + sourceOffset, tr.end, COLOR.PINK, 30); DrawDebugLine(tr.end, targetPos, COLOR.RED, 30); } else { print("ETUtil.TraceArty: ^2Has line of sight to", targetName); DrawDebugLine(sourcePos + sourceOffset, targetPos, COLOR.GREEN, 30); } } } else { Error("ETUtil.TraceArty: No Arty Targets"); } } else { Error("ETUtil.TraceArty: No Arty Goal Close Enough"); } }; /////////////////////////////////////////////////////////////////////////////// // member TraceAllArty = function() { targets = Util.GoalTable("ARTILLERY.*"); sourceOffset = Vector3(0,0,64); foreach( mapgoal in Util.GoalTable("CALLART.*") ) { sourcePos = mapgoal.GetPosition(); found = ""; foreach (targetgoal in targets) { targetPos = targetgoal.GetPosition(); tr = TraceLine(sourcePos + sourceOffset, targetPos, null, TRACE.SHOT, GetLocalEntity(), false); if ( tr.fraction == 1 ) { found += targetgoal.GetName() + " "; } } if ( found == "" ) { Error( mapgoal.GetName(), "has zero visible targets!, try running /bot fgp" ); } else { print( mapgoal.GetName(), "has visible targets", found ); } } }; /////////////////////////////////////////////////////////////////////////////// // member IsTeamDead = function(team, minbots, class) { numalive = 0; // loop through the reserved player ents since there is no team table stored for ( i = 0; i < 64; i += 1 ) { if (EntityIsValid(i) && GetEntTeam(i) == team && !GetEntFlags(i, ENTFLAG.DEAD, ENTFLAG.LIMBO) && (!class || GetEntClass(i)==class)) { numalive += 1; if (!minbots) { return false; } } } if ( (minbots && numalive < minbots) || numalive == 0 ) { return true; } else { return false; } }; /////////////////////////////////////////////////////////////////////////////// // member IsTeamClassDead = function(team, class) { return this.IsTeamDead(team, 0, class); }; /////////////////////////////////////////////////////////////////////////////// // used for setting start indexes for goals that are pathing to offsets member SetStartIndex = function( goalName, startIndex ) { startIndex = ToInt(startIndex, -1); g = GetGoal(goalName); if (g && g.Offsets) { offsetCount = tableCount(g.Offsets); if ( startIndex > offsetCount -1 ) { startIndex = -1; // random } g.StartIndex = startIndex; } else { Util.MapDebugPrint("ETUtil.SetStartIndex: Invalid Goal: " + goalName, 2); } }; /////////////////////////////////////////////////////////////////////////////// // used for excluding indexes for goals that are pathing to offsets // use null or an empty table to re-enable all member SetExcludeIndexes = function( goalName, excludeTable ) { g = GetGoal(goalName); if (g && g.Offsets) { // passing null or an empty table will enable all if ( IsNull(excludeTable) ) { excludeTable = {}; } if ( IsTable(excludeTable) ) { g.ExcludeIndexes = excludeTable; } else { Util.MapDebugPrint("ETUtil.ExcludeIndexes: Expecting table, got " + typeName(excludeTable), 2); } } else { Util.MapDebugPrint("ETUtil.ExcludeIndexes: Invalid Goal: " + goalName, 2); } }; /////////////////////////////////////////////////////////////////////////////// // find a usable corpse starting from the body queue member FindUsableCorpse = function(bot, range) { // search through the body queue entities for ( i = 64; i < 71; i += 1 ) { corpse = GetEntityInSphere(bot.GetPosition(), range, CLASS.CORPSE, i); if ( corpse && bot.GetTeam() != GetEntTeam(corpse) ) { return corpse; } yield(); } return null; }; /////////////////////////////////////////////////////////////////////////////// // member SetAimModeForMount = function(goalName, aimType) { goal = Util.GetGoal(goalName, "ETUtil.SetAimModeForMount"); if ( goal ) { if ( aimType == "opposite" || IsVec3(aimType) || IsTable(aimType) || aimType == "random" || aimType == "velocity" ) { goal.AimMode = aimType; goal.ExtraDebugText = null; // update the render string } else { Util.MapDebugPrint("ETUtil.SetAimModeForMount: invalid aimType: " + aimType, 2 ); } } }; /////////////////////////////////////////////////////////////////////////////// // member SuicideSpawn = function( team, spawn, numbots, class ) { c = 0; b = 0; foreach ( bot in BotTable ) { if ( bot.GetTeam() == team ) { b += 1; // every other bot to avoid low bot count numbers spawning all at a point if ( numbots && numbots != -1 && (b % 2 == 0) ) { continue; } // is it class limited? if ( class && bot.GetClass() != class ) { continue; } c += 1; bot.ChangeSpawnPoint( spawn ); thread(ETUtil.SuicideNextSpawn, bot); if ( numbots && numbots != -1 && c >= numbots ) { return; } } } }; /////////////////////////////////////////////////////////////////////////////// // member GetBotSideArm = function(bot) { team = Util.TeamName(bot.GetTeam()); if ( !Util.TeamSideArms[ team ] ) { return 0; } foreach ( id and weap in Util.TeamSideArms[ team ] ) { if ( bot.HasWeapon( weap ) ) { return weap; } } // shouldn't get here return 0; }; /////////////////////////////////////////////////////////////////////////////// // member GetBotPrimaryWeapon = function(bot, params) { if (!params){ params = { CheckCharged=false, CheckAmmo=false }; } tbl = WeaponTable.GetPrimaryWeapons(bot.GetTeam(), bot.GetClass()); if (tbl) { weap = bot.HasAnyWeapon(tbl, params); if (!weap) { weap = bot.HasAnyWeapon(Util.IndexedSniperWeapons, params); } return weap; } return 0; }; /////////////////////////////////////////////////////////////////////////////// // note: this should be called via thread member SuicideNextSpawn = function( bot ) { while( bot.GetReinforceTime() > 2 ) { yield(); } bot.ExecCommand("kill"); }; /////////////////////////////////////////////////////////////////////////////// // some maps are set up for panzer wars member PanzerMap = function( ) { this.IsPanzereMap = true; // reset the min class counts Util.InitializeClassTables(); foreach ( name and teamId in TEAM ) { if ( name != "SPECTATOR" ) { Server.MinClassCount[teamId][CLASS.SOLDIER] = Server.MaxPlayers; } } foreach ( bot in BotTable ) { this.ChangeToPanzer(bot); //also add to OnBotJoin } }; /////////////////////////////////////////////////////////////////////////////// // member ChangeToPanzer = function( bot ) { if ( bot.GetClass() != CLASS.SOLDIER ) { bot.ChangeClass(CLASS.SOLDIER); yield(); } if ( bot.GetTeam() == TEAM.ALLIES ) { bot.ChangePrimaryWeapon(WeaponTable.AlliedPanzer); } else { bot.ChangePrimaryWeapon(WEAPON.PANZERFAUST); } }; /////////////////////////////////////////////////////////////////////////////// // member DisableRifleNade = function( bot, disable ) { if (!bot ) { return; } foreach( weapon in { WEAPON.M7, WEAPON.GPG40 }) { // get the weapon object w = bot.GetWeapon(weapon); if (!w) { Util.MapDebugPrint("ETUtil.DisableRifleNade: bot does not have rifle nade weapon", 2); continue; } // note: this will do nothing if weapon script sets default to 0.0 // this is to avoid scripts like the plant goal stomping on user // defined settings. if ( disable ) { if ( w.PrimaryFire.DefaultDesirability > 0.1 ) { w.PrimaryFire.Bias = 0.0; w.PrimaryFire.SetDesirabilityRange(0, 2600, 0.0); } } else { if ( w.PrimaryFire.Bias < 0.1 ) { w.PrimaryFire.Bias = 1.0; w.PrimaryFire.SetDesirabilityRange(0, 2600, 0.8); } } } }; /////////////////////////////////////////////////////////////////////////////// // randomly global chat some victory message member WinningChat = function( team ) { Util.RandomChat(team, { VOICE.G_CHEER, VOICE.G_ENEMY_WEAK, VOICE.G_AFFIRMATIVE, VOICE.G_GOODGAME, VOICE.G_GREATSHOT }); }; // randomly global chat some displeasure about losing member LosingChat = function( team ) { Util.RandomChat(team, { VOICE.G_NEGATIVE, VOICE.G_OOPS, VOICE.G_GOODGAME, VOICE.G_HOLD_FIRE }); }; /////////////////////////////////////////////////////////////////////////////// // member CheckTripminePosition = function() { // must be within 64 units of wall tr = Util.TraceFromPlayer(64, TRACE.SHOT); if ( tr.fraction >= 1.0 ) { return false; } // wire distance is max of 512 units tr2 = Util.TraceFromPlane(tr.end, tr.normal, 512); if ( tr2.fraction >= 1.0 ) { return false; } // original trace position is used for the goals aim position return tr.end; }; /////////////////////////////////////////////////////////////////////////////// //DEPRECATE member RandomSpawn = function( team, spawn ) { Util.MapDeprecateMessage( "ETUtil.RandomSpawn", "Util.RandomSpawn" ); Util.RandomSpawn(team, spawn); }; member ChangeSpawn = function( team, spawn, numbots ) { Util.MapDeprecateMessage( "ETUtil.ChangeSpawn", "Util.ChangeSpawn" ); Util.ChangeSpawn(team, spawn, numbots); }; member EnableGoal = function( goal ) { Util.MapDeprecateMessage( "ETUtil.EnableGoal", "Util.EnableGoal" ); SetAvailableMapGoals( 0, true, goal ); }; member LimitGoal = function( team, goal ) { Util.MapDeprecateMessage( "ETUtil.LimitGoal", "Util.LimitGoal" ); Util.LimitGoal(team, goal); }; member DisableGoal = function( goal, routes ) { Util.MapDeprecateMessage( "ETUtil.DisableGoal", "Util.DisableGoal" ); Util.DisableGoal(goal, routes); }; member CountTeam = function( team ) { Util.MapDeprecateMessage( "ETUtil.CountTeam", "Server.Team[ teamId ].NumBots" ); return Server.Team[team].NumBots; }; member ShowActiveGoals = function(routes) { Util.MapDeprecateMessage( "ETUtil.ShowActiveGoals", "Util.ShowActiveGoals" ); }; member ChangeClass = function( team, originalclass, newclass, revert, maxbots ) { Util.MapDeprecateMessage( "ETUtil.ChangeClass", "Server.MinClassCount" ); }; member LimitToClass = function( goalname, team, class1, class2, class3, class4 ) { Util.MapDeprecateMessage( "ETUtil.LimitToClass", "Util.LimitToClass" ); Util.LimitToClass(goalname, team, class1, class2, class3, class4); }; member ExcludeClass = function( goalname, team, class1, class2, class3, class4 ) { Util.MapDeprecateMessage( "ETUtil.ExcludeClass", "Util.ExcludeClass" ); Util.ExcludeClass(goalname, team, class1, class2, class3, class4); };