rendered paste body//
// 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:) <mapname>.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" );
}
};