// // MegaTF Coop: Last Man Standing // by XavioR // // LMS: The player(s) are spawned in an area on a map and have X number of lives. // The objective is to defend against waves of increasingly difficult monsters while // not losing all lives. // Map requirements: // 1) "info_lmsdetect" : tells the game we're playing LMS and precache's all monsters - also sets up lives over "info_tfdetect" // 2) "func_monsterspawn" : entities from which monsters will spawn - monsters will then move towards the "trigger" entity // 3) (Optional:) .tfi - specifies all LMS map info inside a .tfi (see lms_keep.tfi for more info) float last_level; #define LMS_PREMATCH_TIME 20 // amount of time players have before the first wave of monsters #define LMS_MATCH_TIME 60 // how long players have until the next wave spawns. #define LMS_LAST_LEVEL last_level #define LMS_REST_TIME 15 // rest time in between levels #define spawnfunc th_fireball #define spawn_starttime speed_time #define spawn_active speed_finished #define spawn_tries monsterflags #define lms_starttime monster_items #define lms_level lives #define lms_startteamlives ammo_shells //#define lms_game_begun health_attr // defs.pqc'd //#define lms_useroundlives armor_attr // "" //#define lms_usegloballives damage_attr // "" //#define lms_lives is_malfunctioning // "" #define lms_replenish_lives monster_items #define lms_startmatch_numplayers lms_entity.misc_attr #define lms_override_speed super_damage_finished #ifdef mtf_coop_expsys // read data from .tfi instead of using hard-coded stuff. This needs coop_rankings, because onlt servers that are coop_rankings-compatable //can use extensions like fopen/close/etc #define lms_readfile reload_attr #endif void () lms_goalie_think; void () spawnwave; void ( entity newmon, float m_level ) GetMonsterSpawn; void ( entity newmon, string newmon_cln ) SetSpawnFunc; void ( entity m_precache ) SetupMonsterPrecache = { local entity oself; oself = self; self = m_precache; m_precache.#spawnfunc (); m_precache.nextthink = time + 666; self = oself; }; void () pre_spawnwave = { // XavioR: TODO: eh, this is flawed - what happens when players join/leave? bprint( 2, "Last Man Standing is beginning! Setting team lives to: " ); bprint( 2, ftos( self.#lms_startteamlives/* + ceil( (#lms_startmatch_numplayers - 1) * 1.25) */) ); bprint( 2, "\n" ); if (self.#lms_useroundlives > 0) { bprint( 2, "Player lives (per round): " ); bprint( 2, ftos( self.#lms_useroundlives ) ); bprint( 2, "\n" ); } else { bprint( 2, "Player lives (global): " ); bprint( 2, ftos( self.#lms_usegloballives ) ); bprint( 2, "\n" ); } team1lives = self.#lms_startteamlives;// + ceil( (#lms_startmatch_numplayers - 1) * 1.25); newmis = spawn (); // new entity to keep track of player lives (game goalie) newmis.#lms_startteamlives = self.#lms_startteamlives; newmis.think = lms_goalie_think; newmis.classname = "lms_goalie"; newmis.nextthink = time + 5; self.think = spawnwave; self.#lms_game_begun = #TRUE; // now players can't join if #lms_usegloballives > 0 spawnwave(); }; void () wait_for_players = { if (num_players > 0) { bprint( 2, "Last Man Standing: Waiting for all players to join (" ); bprint( 2, ftos( #LMS_PREMATCH_TIME ) ); bprint( 2, " seconds)\n" ); self.nextthink = time + #LMS_PREMATCH_TIME; self.think = pre_spawnwave; } else { self.think = wait_for_players; self.nextthink = time + 1; } } void () info_lmsdetect = { local entity m_precache; #LMS_LAST_LEVEL = 14; #ifdef mtf_coop_expsys local string fname, a; local float file, default_spawn; fname = frik_strcat( "maps/", mapname, ".tfi" ); file = open (fname, 0); if ( file == -1 ) { bprint( 2, "No .tfi for " ); bprint( 2, mapname ); bprint( 2, " - using default monster info\n" ); self.#lms_readfile = #FALSE; } else { self.#lms_readfile = #TRUE; // Get map rules a = ""; do { a = read (file); if ( a == "//END" ) { bprint (2, "info_lmsdetect: Game rules not found in .tfi - fix your files!!\n"); self.#lms_readfile = #FALSE; break; } if ( a == "// GAME RULES" ) { a = read (file); a = read (file); self.#lms_startteamlives = stof(a); a = read (file); a = read (file); self.#lms_useroundlives = stof(a); a = read (file); a = read (file); self.#lms_usegloballives = stof(a); a = read (file); a = read (file); #LMS_LAST_LEVEL = stof(a); a = read (file); a = read (file); if (a == "default") default_spawn = #TRUE; break; } } while (1); close (file); } #endif if (lms_entity != world) { bprint( 2, "info_lmsdetect: too many info_lmsdetect entities in this map - you only need one.\n" ); dremove(self); } lms_entity = self; m_precache = spawn(); self.goalentity = m_precache; #ifdef mtf_coop_expsys if (self.#lms_readfile == #TRUE && default_spawn == #FALSE) { file = open (fname, 0); bprint(2,"NOT using def spawn!\n"); // Get monster precache's a = ""; do { a = read (file); if ( a == "precache_end" ) { dprint ("info_lmsdetect: Monsters precache'd!\n"); break; } if ( a == "//END" ) { bprint (2, "info_lmsdetect: Missing precache_end in .tfi - fix your file!\n"); self.#lms_readfile = #FALSE; break; } if ( a == "precache_set" ) { a = read (file); while (a != "precache_end" && a != "//END") { SetSpawnFunc( m_precache, a ); SetupMonsterPrecache( m_precache ); a = read (file); } break; } } while (1); close(file); } if (self.#lms_readfile == #FALSE || default_spawn == #TRUE) { #endif bprint(2,"using def spawn!\n"); // do all the precache's by calling the monsters' spawn functions m_precache.#spawnfunc = monster_army; SetupMonsterPrecache( m_precache ); m_precache.#spawnfunc = monster_dog; SetupMonsterPrecache( m_precache ); m_precache.#spawnfunc = monster_enforcer; SetupMonsterPrecache( m_precache ); m_precache.#spawnfunc = monster_demon1; SetupMonsterPrecache( m_precache ); m_precache.#spawnfunc = monster_fish; // who knows, we might have a moat :p SetupMonsterPrecache( m_precache ); m_precache.#spawnfunc = monster_knight; SetupMonsterPrecache( m_precache ); m_precache.#spawnfunc = monster_hell_knight; SetupMonsterPrecache( m_precache ); m_precache.#spawnfunc = monster_ogre; SetupMonsterPrecache( m_precache ); m_precache.#spawnfunc = monster_shalrath; SetupMonsterPrecache( m_precache ); m_precache.#spawnfunc = monster_tarbaby; SetupMonsterPrecache( m_precache ); m_precache.#spawnfunc = monster_wizard; SetupMonsterPrecache( m_precache ); m_precache.#spawnfunc = monster_zombie; SetupMonsterPrecache( m_precache ); m_precache.#spawnfunc = monster_shambler; SetupMonsterPrecache( m_precache ); #ifdef mtf_coop_expsys } #endif // TODO: add hipnotic/travail monsters too dremove(m_precache); // k, we're done precache'ing - bai! // setup lives, but add/detract 'em based on player count if ( self.#lms_startteamlives == 0 ) self.#lms_startteamlives = 10; team1lives = self.#lms_startteamlives; if (self.#lms_useroundlives == 0) self.#lms_useroundlives = 5; // set a default value for lives per round (replenishes every round) if (self.#lms_usegloballives > 0) self.#lms_useroundlives = 0; // set global lives for players only (look in tforttm.qc) total_monsters -= 13; self.#lms_starttime = time; self.#lms_level = 1; wait_for_players (); }; void () lms_goalie_think = { local float p_scale; local entity te; // find all resupply packs and adjust their wait time according to num_players #lms_startmatch_numplayers = 0; te = find ( world, classname, "player" ); while (te) { if (te.playerclass != 0/* || te.#lms_replenish_lives == 1 */) #lms_startmatch_numplayers = #lms_startmatch_numplayers + 1; te = find (te, classname, "player"); } if ( team1lives <= 0 || #lms_startmatch_numplayers == 0 ) { bprint(2, "BLUE TEAM HAS NO MORE LIVES - MONSTERS WIN\n"); te = find ( world, classname, "func_monsterspawn" ); while (te) { te.#spawn_active = 0; te = find (te, classname, "func_monsterspawn"); } execute_changelevel (); self.nextthink = time + 999999999; return; } p_scale = ( 0.15 * (#lms_startmatch_numplayers - 1) ) + 1; // allow more monsters if there are more players (cuz they spawn faster) //p_scale = 1; if ( (total_monsters - killed_monsters) > ceil(p_scale * 10) ) { bprint( 2, "MONSTERS ARE OVER-RUNNING BLUE OUTPOST (" ); bprint( 2, ftos( ceil(p_scale * 10) ) ); bprint( 2, ") - ONE LIFE IS LOST!\n" ); team1lives = team1lives - 1; bprint( 2, "REMAINING TEAM LIVES: " ); bprint( 2, ftos( team1lives ) ); bprint( 2, "\n" ); } self.think = lms_goalie_think; self.nextthink = time + 5; }; void () spawnwave_resttime = { local entity te; te = find ( world, classname, "func_monsterspawn" ); while (te) { te.#spawn_active = 0; te = find (te, classname, "func_monsterspawn"); } // if we're using lives-per-round, reset all connected players' lives if (self.#lms_useroundlives > 0) { self.#lms_game_begun = #FALSE; te = find ( world, classname, "player" ); while (te) { if (te.playerclass != 0 || te.#lms_replenish_lives == 1 ) { te.#lms_lives = self.#lms_useroundlives; // te.#lms_replenish_lives = 0; te.lives = -1; // we don't use these.. } te = find (te, classname, "player"); } } if ( team1lives <= 0 ) return; // TODO: fix cheesy messages bprint( 2, "WAVE " ); bprint( 2, ftos( self.#lms_level - 1 ) ); bprint( 2, " COMPLETE, PREPARE FOR NEXT WAVE!\n" ); self.think = spawnwave; self.nextthink = time + #LMS_REST_TIME; }; void () spawnwave = { local entity te; local float exptotal; if ( team1lives <= 0 ) return; if (self.#lms_level == 0) { bprint( 2, "spawnwave: invalid lms level\n" ); return; } if (self.#lms_level > #LMS_LAST_LEVEL) { bprint( 2, "PLAYERS WIN!\n" ); exptotal = killed_monsters * 3; // EXP 4 ALLZ! te = find ( world, classname, "player" ); while (te) { if (te.coop_exp != -1 && /*te != other && */te.has_disconnected != 1) { //te.coop_exp = te.coop_exp + exptotal; MTF_Coop_AddExperience(te, world, exptotal); MTF_Coop_PreWriteExperience( te, te.connect_name, te.coop_exp ); stuffcmd(te, "cmd sayexp\n"); } te = find (te, classname, "player"); } execute_changelevel (); self.nextthink = time + 999999999; return; } self.#lms_game_begun = #TRUE; // TODO: do the fancy lettering crap.. bprint( 2, "PREPARE FOR WAVE " ); bprint( 2, ftos( self.#lms_level ) ); bprint( 2, " OF LMS!\n" ); te = find ( world, classname, "func_monsterspawn" ); while (te) { te.#spawn_starttime = time; te.#lms_level = self.#lms_level; te.#spawn_active = 1; te = find (te, classname, "func_monsterspawn"); } self.#lms_level++; self.think = spawnwave_resttime; self.nextthink = time + #LMS_MATCH_TIME; }; // get a multiplier value for how fast/slow monsters will respawn float ( float m_level ) GetLevelTiming = { #ifdef mtf_coop_expsys if ( lms_entity.#lms_override_speed > 0 ) { return lms_entity.#lms_override_speed; } #endif if ( m_level == 8 ) // lots and lots of dogs :D return .5; else if ( m_level == 5 ) // lots of zombies and knights :D return 1; else if ( m_level == 10 ) // spaaaaaawwwwwnnnnsssss return .85; else if ( m_level == 2 || m_level == 3 || m_level == 4 ) return 1.25; else if ( m_level == 1 ) return 1; else { if ( m_level > 12 ) return 1.1; else return 1.25;//m_level * .5; } }; void () func_monsterspawn = { local float rndthink, rndspawn, p_scale; local entity newmon; if ( !self.target ) dprint( "func_monsterspawn: no target - can't tell monsters where to go!\n" ); if ( self.#spawn_active == 0 || self.#lms_level == 0 ) { self.nextthink = time + 1; self.think = func_monsterspawn; return; } // self.#lms_level // self.#spawn_tries rndspawn = random(); rndthink = rint(random() * 6 + 1); //bprint(2,ftos(rndthink)); //bprint(2," is next think \n"); if ( rndspawn < .5 || self.#spawn_tries == 3 ) { self.#spawn_tries = 0; newmon = spawn(); total_monsters += 1; // WriteByte (2.000000, 27.000000); WriteByte (MSG_ALL, 38); WriteByte (MSG_ALL, 12); WriteLong (MSG_ALL, total_monsters); newmon.target = self.target; GetMonsterSpawn( newmon, self.#lms_level ); newmon.#npc_spawndelay = 1; newmon.angles = self.#npc_origangle; newmon.#npc_origangle = self.#npc_origangle; // newmon.netname = self.netname; // newmon.classname = self.classname; // newmon.old_owner = self; newmon.is_monster = 1; newmon.nextthink = time + 1; newmon.think = respawn_think; newmon.lives = -1; // so we auto-remove the dead bodies (check respawn_think()) and so no exp is awarded till game's end if (mapname == "lms_keep") // hack setorigin( newmon, self.origin + '0 0 65' ); else setorigin( newmon, self.origin ); } else self.#spawn_tries = self.#spawn_tries + 1; rndthink = rndthink * GetLevelTiming( self.#lms_level ); p_scale = ( 0.15 * (#lms_startmatch_numplayers - 1) ) + 1; // if more players, make monsters spawn faster //bprint(2, ftos(p_scale)); //bprint(2, "\n"); rndthink = rndthink / p_scale; self.think = func_monsterspawn; self.nextthink = time + rndthink; }; void ( entity newmon, float m_level ) GetMonsterSpawn = { local float rnd; local string m_cln; rnd = random(); #ifdef mtf_coop_expsys local string lname, fname, a, a1, a2, a3; local float file, c1, c2, c3; lname = strcat( "level_", ftos(lms_entity.#lms_level - 1) ); if ( lms_entity.#lms_readfile == #TRUE ) { fname = frik_strcat( "maps/", mapname, ".tfi" ); file = open (fname, 0); // Get Monsters From File a = ""; do { a = read (file); if ( a == "//END" ) { bprint (2, "info_lmsdetect: No monsters found - add some!\n"); self.#lms_readfile = #FALSE; break; } if ( a == lname ) { a = read (file); a = read (file); lms_entity.#lms_override_speed = stof(a); a = read (file); a1 = a; a1 = strzone(a1); a = read (file); c1 = stof(a); a = read (file); a2 = a; a2 = strzone(a2); a = read (file); c2 = stof(a); a = read (file); a3 = a; a3 = strzone(a3); a = read (file); c3 = stof(a); break; } } while (1); close (file); while ( 1 ) { // TODO: make sure this doesn't break somehow? if (rnd > c1) m_cln = a1; else if (rnd > c2) m_cln = a2; else if (rnd > c3) m_cln = a3; rnd = random(); if ( m_cln != "monster_none" ) break; } #ifdef DEBUG dprint(m_cln ); dprint(" is spawning\n"); #endif } else { #endif if ( m_level == 1 ) { if (rnd > .7) m_cln = "monster_army"; else m_cln = "monster_dog"; } else if ( m_level == 2 ) { if (rnd > .4) m_cln = "monster_army"; else m_cln = "monster_dog"; } else if ( m_level == 3 ) { if (rnd > .4) m_cln = "monster_army"; else m_cln = "monster_enforcer"; } else if ( m_level == 4 ) { m_cln = "monster_enforcer"; } else if ( m_level == 5 ) { if (rnd > .6) m_cln = "monster_zombie"; else m_cln = "monster_knight"; } else if ( m_level == 6 ) { if (rnd > .4) m_cln = "monster_knight"; else m_cln = "monster_hell_knight"; } else if ( m_level == 7 ) { if (rnd > .8) m_cln = "monster_ogre"; else if (rnd > .3) m_cln = "monster_hell_knight"; else m_cln = "monster_knight"; } else if ( m_level == 8 ) { m_cln = "monster_dog"; // really fast respawning dogs :p } else if ( m_level == 9 ) { if (rnd > .8) m_cln = "monster_tarbaby"; else if (rnd > .5) m_cln = "monster_ogre"; else m_cln = "monster_hell_knight"; } else if ( m_level == 10 ) { m_cln = "monster_tarbaby"; } else if ( m_level == 11 ) { if (rnd > .5) m_cln = "monster_shalrath"; else m_cln = "monster_knight"; } else if ( m_level == 12 ) { if (rnd > .7) m_cln = "monster_demon1"; else if (rnd > .4) m_cln = "monster_shalrath"; else m_cln = "monster_wizard"; } else if ( m_level == 13 ) { if (rnd > .5) m_cln = "monster_demon1"; else if (rnd > .4) m_cln = "monster_shambler"; else m_cln = "monster_wizard"; } else if ( m_level == 14 ) { if (rnd > .5) m_cln = "monster_shambler"; else m_cln = "monster_zombie"; } #ifdef mtf_coop_expsys } #endif SetSpawnFunc( newmon, m_cln ); }; // set spawn and netname info based on classname // Add any new monsters here, etc. void ( entity newmon, string newmon_cln ) SetSpawnFunc = { newmon.classname = newmon_cln; if (newmon_cln == "monster_dog") { newmon.#spawnfunc = monster_dog; newmon.think1 = monster_dog1; newmon.netname = "rottweiler"; newmon.classname = "monster_dog"; } else if (newmon_cln == "monster_army") { newmon.#spawnfunc = monster_army; newmon.think1 = monster_army1; newmon.netname = "grunt"; newmon.classname = "monster_army"; } else if (newmon_cln == "monster_knight") { newmon.#spawnfunc = monster_knight; newmon.think1 = monster_knight1; newmon.netname = "knight"; newmon.classname = "monster_knight"; } else if (newmon_cln == "monster_hell_knight") { newmon.#spawnfunc = monster_hell_knight; newmon.think1 = monster_hell_knight1; newmon.netname = "hell knight"; newmon.classname = "monster_hell_knight"; } else if (newmon_cln == "monster_demon1") { newmon.#spawnfunc = monster_demon1; newmon.think1 = monster_demon2; newmon.netname = "fiend"; newmon.classname = "monster_demon1"; } else if (newmon_cln == "monster_shambler") { newmon.#spawnfunc = monster_shambler; newmon.think1 = monster_shambler1; newmon.netname = "shambler"; newmon.classname = "monster_shambler"; } else if (newmon_cln == "monster_wizard") { newmon.#spawnfunc = monster_wizard; newmon.think1 = monster_wizard1; newmon.netname = "scrag"; newmon.classname = "monster_wizard"; } else if (newmon_cln == "monster_enforcer") { newmon.#spawnfunc = monster_enforcer; newmon.think1 = monster_enforcer1; newmon.netname = "enforcer"; newmon.classname = "monster_enforcer"; } else if (newmon_cln == "monster_shalrath") { newmon.#spawnfunc = monster_shalrath; newmon.think1 = monster_shalrath1; newmon.netname = "vore"; newmon.classname = "monster_shalrath"; } else if (newmon_cln == "monster_tarbaby") { newmon.#spawnfunc = monster_tarbaby; newmon.think1 = monster_tarbaby1; newmon.netname = "spawn"; newmon.classname = "monster_tarbaby"; } else if (newmon_cln == "monster_ogre") { newmon.#spawnfunc = monster_ogre; newmon.think1 = monster_ogre1; newmon.netname = "ogre"; newmon.classname = "monster_ogre"; } else if (newmon_cln == "monster_fish") { newmon.#spawnfunc = monster_fish; newmon.think1 = monster_fish1; newmon.netname = "rotfish"; newmon.classname = "monster_fish"; } else if (newmon_cln == "monster_zombie") { newmon.#spawnfunc = monster_zombie; newmon.think1 = monster_zombie1; newmon.netname = "zombie"; newmon.classname = "monster_zombie"; // HIPNOTIC } else if (newmon_cln == "monster_scourge") { newmon.#spawnfunc = monster_scourge; newmon.think1 = monster_scourge1; newmon.netname = "centroid"; } else if (newmon_cln == "monster_gremlin") { newmon.#spawnfunc = monster_gremlin; newmon.think1 = monster_gremlin1; newmon.netname = "gremlin"; // ROGUE } else if (newmon_cln == "monster_morph") { newmon.#spawnfunc = monster_morph; newmon.think1 = monster_morph1; newmon.netname = "morpher"; } else if (newmon_cln == "monster_wrath") { newmon.#spawnfunc = monster_wrath; newmon.think1 = monster_wrath1; newmon.netname = "wrath"; } else if (newmon_cln == "monster_eel") { newmon.#spawnfunc = monster_eel; newmon.think1 = monster_eel1; newmon.netname = "eel"; // TRAVAIL } else if (newmon_cln == "monster_juggernaut") { newmon.#spawnfunc = monster_juggernaut; newmon.think1 = monster_juggernaut1; newmon.netname = "juggernaut"; } else if (newmon_cln == "monster_spider") { newmon.#spawnfunc = monster_spider; newmon.think1 = monster_spider1; newmon.netname = "poisonous spider"; } else { bprint(2, "SetSpawnFunc: Invalid or unsupported monster classname: "); bprint(2, newmon_cln ); bprint(2, " - fix your .tfi!\n" ); } };