From 21fb564ff1131f208d7a1b01100690326ee5a7ce Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 30 Aug 2015 13:36:59 +1000 Subject: [PATCH] Add rocketminsta, vampirehook & breakablehook mutators, also clear out some unused files --- effectinfo.txt | 145 ++ mutators.cfg | 42 + qcsrc/client/weapons/projectile.qc | 5 + qcsrc/common/constants.qh | 2 + qcsrc/common/notifications.inc | 1 + qcsrc/common/weapons/w_vaporizer.qc | 184 +- qcsrc/server/autocvars.qh | 18 + qcsrc/server/g_tetris.qc | 1264 --------- .../server/mutators/mutator_breakablehook.qc | 24 + qcsrc/server/mutators/mutator_instagib.qc | 8 + qcsrc/server/mutators/mutator_rocketminsta.qc | 33 + qcsrc/server/mutators/mutator_vampirehook.qc | 36 + qcsrc/server/mutators/mutators_include.qc | 3 + qcsrc/server/t_plats.qc | 2275 ----------------- qcsrc/server/target_music.qc | 137 - 15 files changed, 494 insertions(+), 3683 deletions(-) delete mode 100644 qcsrc/server/g_tetris.qc create mode 100644 qcsrc/server/mutators/mutator_breakablehook.qc create mode 100644 qcsrc/server/mutators/mutator_rocketminsta.qc create mode 100644 qcsrc/server/mutators/mutator_vampirehook.qc delete mode 100644 qcsrc/server/t_plats.qc delete mode 100644 qcsrc/server/target_music.qc diff --git a/effectinfo.txt b/effectinfo.txt index 7990d74d7..2ba9a413b 100644 --- a/effectinfo.txt +++ b/effectinfo.txt @@ -9599,3 +9599,148 @@ airfriction 0.2 liquidfriction 0.8 originjitter 8 8 32 velocityjitter 64 64 32 + +// rocketminsta laser trail - red +effect rocketminsta_laser_red +trailspacing 2 +type static +color 0xFF0F0F 0xFF0F0F +size 3 3 +tex 38 38 +alpha 256 256 968 +//velocityjitter 16 16 16 +lightradius 90 +lighttime 0 +lightcolor 1.5 3 6 +velocitymultiplier -0.1 +// bright sparks +effect rocketminsta_laser_red +trailspacing 12 +count 1.5 +type snow +tex 42 42 +color 0xFF0F0F 0xFF0F0F +size 2 4 +sizeincrease -20 +alpha 444 512 1866 +bounce 1 +velocityoffset 0 0 15 +airfriction 12 +originjitter 1 1 1 +velocityjitter 50 50 50 + +// rocketminsta laser trail - blue +effect rocketminsta_laser_blue +trailspacing 2 +type static +color 0x0F0FFF 0x0F0FFF +size 3 3 +tex 38 38 +alpha 256 256 968 +//velocityjitter 16 16 16 +lightradius 90 +lighttime 0 +lightcolor 1.5 3 6 +velocitymultiplier -0.1 +// bright sparks +effect rocketminsta_laser_blue +trailspacing 12 +count 1.5 +type snow +tex 42 42 +color 0x0F0FFF 0x0F0FFF +size 2 4 +sizeincrease -20 +alpha 444 512 1866 +bounce 1 +velocityoffset 0 0 15 +airfriction 12 +originjitter 1 1 1 +velocityjitter 50 50 50 + +// rocketminsta laser trail - yellow +effect rocketminsta_laser_yellow +trailspacing 2 +type static +color 0xFFFF0F 0xFFFF0F +size 3 3 +tex 38 38 +alpha 256 256 968 +//velocityjitter 16 16 16 +lightradius 90 +lighttime 0 +lightcolor 1.5 3 6 +velocitymultiplier -0.1 +// bright sparks +effect rocketminsta_laser_yellow +trailspacing 12 +count 1.5 +type snow +tex 42 42 +color 0xFFFF0F 0xFFFF0F +size 2 4 +sizeincrease -20 +alpha 444 512 1866 +bounce 1 +velocityoffset 0 0 15 +airfriction 12 +originjitter 1 1 1 +velocityjitter 50 50 50 + +// rocketminsta laser trail - pink +effect rocketminsta_laser_pink +trailspacing 2 +type static +color 0xFF0FFF 0xFF0FFF +size 3 3 +tex 38 38 +alpha 256 256 968 +//velocityjitter 16 16 16 +lightradius 90 +lighttime 0 +lightcolor 1.5 3 6 +velocitymultiplier -0.1 +// bright sparks +effect rocketminsta_laser_pink +trailspacing 12 +count 1.5 +type snow +tex 42 42 +color 0xFF0FFF 0xFF0FFF +size 2 4 +sizeincrease -20 +alpha 444 512 1866 +bounce 1 +velocityoffset 0 0 15 +airfriction 12 +originjitter 1 1 1 +velocityjitter 50 50 50 + +// rocketminsta laser trail - neutral +effect rocketminsta_laser_neutral +trailspacing 2 +type static +color 0xFFFFFF 0xFFFFFF +size 3 3 +tex 38 38 +alpha 256 256 968 +//velocityjitter 16 16 16 +lightradius 90 +lighttime 0 +lightcolor 1.5 3 6 +velocitymultiplier -0.1 +// bright sparks +effect rocketminsta_laser_neutral +trailspacing 12 +count 1.5 +type snow +tex 42 42 +color 0xFFFFFF 0xFFFFFF +size 2 4 +sizeincrease -20 +alpha 444 512 1866 +bounce 1 +velocityoffset 0 0 15 +airfriction 12 +originjitter 1 1 1 +velocityjitter 50 50 50 diff --git a/mutators.cfg b/mutators.cfg index eeecb9f6a..311debc2c 100644 --- a/mutators.cfg +++ b/mutators.cfg @@ -345,3 +345,45 @@ set g_buffs_swapper_range 1500 "maximum range of swapping with enemy" set g_buffs_magnet 0 "magnet buff: greatly increased item pickup range" set g_buffs_magnet_time 60 "magnet buff carry time" set g_buffs_magnet_range_item 450 "item pickup range" + + +// ============== +// vampire hook +// ============== +set g_vampirehook 0 "enable vampire hooks mutator (grappling hook steals enemy damage, heals teammates)" +set g_vampirehook_damage "2" "hook damage" +set g_vampirehook_damagerate "0.2" "hook damage delay" +set g_vampirehook_health_steal "2" "give hooker this much health per damage frame" +set g_vampirehook_teamheal "1" "hooking teammates drains hooker's health" + + +// =============== +// rocket minsta +// =============== +set g_rm 0 "enable rocket minsta (explosive vaporizer primary, secondary laser prong)" +set g_rm_damage "70" "direct hit damage" +set g_rm_edgedamage "38" "edge damage" +set g_rm_force "400" "force" +set g_rm_radius "140" "explosion radius" +set g_rm_laser "1" "enable team colored lasers" +set g_rm_laser_count "3" "number of rocketminsta lasers" +set g_rm_laser_speed "6000" "laser speed" +set g_rm_laser_spread "0.05" "laser spread" +set g_rm_laser_zspread "0" "vertical spread of group shots" +set g_rm_laser_spread_random "0" "randomise rocketminsta laser spread" +set g_rm_laser_lifetime "30" "laser lifetime" +set g_rm_laser_damage "80" "laser damage, divided by laser count" +set g_rm_laser_refire "0.7" "laser refire" +set g_rm_laser_rapid "1" "fast refire after holding down laser for certain amount of time" +set g_rm_laser_rapid_refire "0.35" "rapid laser refire" +set g_rm_laser_rapid_delay "0.6" "delay before turning on rapid laser mode" +set g_rm_laser_rapid_animtime "0.3" "animation time for rapid laser" +set g_rm_laser_radius "150" "radius of rocketminsta laser explosion" +set g_rm_laser_force "400" "laser force, divided by laser count" + + +// ================ +// breakable hook +// ================ +set g_breakablehook 0 "enable breakable hook mutator (hook can be damaged, and returns damage to the owner when broken)" +set g_breakablehook_owner 0 "allow owner to break their own hook" diff --git a/qcsrc/client/weapons/projectile.qc b/qcsrc/client/weapons/projectile.qc index c85347dff..573d55633 100644 --- a/qcsrc/client/weapons/projectile.qc +++ b/qcsrc/client/weapons/projectile.qc @@ -270,6 +270,9 @@ void Ent_Projectile() if(f & 2) { + string rm_suffix = strcat("rocketminsta_laser_", Static_Team_ColorName_Lower(self.team)); + if(_particleeffectnum(rm_suffix) < 0 || Team_TeamToNumber(self.team) == -1) { rm_suffix = "TR_NEXUIZPLASMA"; } + self.cnt = ReadByte(); self.silent = (self.cnt & 0x80); @@ -317,6 +320,8 @@ void Ent_Projectile() case PROJECTILE_RPC: setmodel(self, W_Model("ok_rocket"));self.traileffect = particleeffectnum(EFFECT_TR_ROCKET); break; + case PROJECTILE_ROCKETMINSTA_LASER: setmodel(self, "models/elaser.mdl");self.traileffect = _particleeffectnum(rm_suffix); break; + default: if(MUTATOR_CALLHOOK(Ent_Projectile, self)) break; diff --git a/qcsrc/common/constants.qh b/qcsrc/common/constants.qh index 6d332d611..fe04e910c 100644 --- a/qcsrc/common/constants.qh +++ b/qcsrc/common/constants.qh @@ -292,6 +292,8 @@ const int PROJECTILE_BUMBLE_BEAM = 31; const int PROJECTILE_MAGE_SPIKE = 32; const int PROJECTILE_SHAMBLER_LIGHTNING = 33; +const int PROJECTILE_ROCKETMINSTA_LASER = 34; + // projectile IDs 40-50 reserved const int PROJECTILE_RPC = 60; diff --git a/qcsrc/common/notifications.inc b/qcsrc/common/notifications.inc index a13fa0b04..1f553c796 100644 --- a/qcsrc/common/notifications.inc +++ b/qcsrc/common/notifications.inc @@ -597,6 +597,7 @@ MSG_CENTER_NOTIF(1, CENTER_LMS_NOLIVES, 0, 0, "", CPID_LMS, "0 0", _("^BGYou have no lives left, you must wait until the next match"), "") MSG_CENTER_NOTIF(1, CENTER_MISSING_TEAMS, 0, 1, "missing_teams", CPID_MISSING_TEAMS, "-1 0", _("^BGWaiting for players to join...\nNeed active players for: %s"), "") MSG_CENTER_NOTIF(1, CENTER_MISSING_PLAYERS, 0, 1, "f1", CPID_MISSING_PLAYERS, "-1 0", _("^BGWaiting for %s player(s) to join..."), "") + MSG_CENTER_NOTIF(1, CENTER_INSTAGIB_DOWNGRADE, 0, 0, "", CPID_INSTAGIB_FINDAMMO,"5 0", _("^BGYour weapon has been downgraded until you find some ammo!"), "") MSG_CENTER_NOTIF(1, CENTER_INSTAGIB_FINDAMMO, 0, 0, "", CPID_INSTAGIB_FINDAMMO,"1 9", _("^F4^COUNT^BG left to find some ammo!"), "") MSG_CENTER_NOTIF(1, CENTER_INSTAGIB_FINDAMMO_FIRST, 0, 0, "", CPID_INSTAGIB_FINDAMMO,"1 10", _("^BGGet some ammo or you'll be dead in ^F4^COUNT^BG!"), _("^BGGet some ammo! ^F4^COUNT^BG left!")) MSG_CENTER_NOTIF(1, CENTER_INSTAGIB_LIVES_REMAINING, 0, 1, "f1", NO_CPID, "0 0", _("^F2Extra lives remaining: ^K1%s"), "") diff --git a/qcsrc/common/weapons/w_vaporizer.qc b/qcsrc/common/weapons/w_vaporizer.qc index f61960594..4ed2dc2d3 100644 --- a/qcsrc/common/weapons/w_vaporizer.qc +++ b/qcsrc/common/weapons/w_vaporizer.qc @@ -45,6 +45,11 @@ REGISTER_WEAPON( VAPORIZER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) .float vaporizer_lasthit; .float jump_interval; +.float jump_interval2; +.bool held_down; +.float rm_force; +.float rm_damage; +.float rm_edmg; #endif #endif #ifdef IMPLEMENTATION @@ -52,6 +57,17 @@ VAPORIZER_SETTINGS(WEP_ADD_CVAR, WEP_ADD_PROP) void spawnfunc_weapon_vaporizer(void) { weapon_defaultspawnfunc(WEP_VAPORIZER.m_id); } void spawnfunc_weapon_minstanex(void) { spawnfunc_weapon_vaporizer(); } +void W_RocketMinsta_Explosion(vector loc) +{ + if(accuracy_canbegooddamage(self)) + accuracy_add(self, WEP_DEVASTATOR.m_id, autocvar_g_rm_damage, 0); + entity dmgent = spawn(); + dmgent.owner = dmgent.realowner = self; + setorigin(dmgent, loc); + RadiusDamage (dmgent, self, autocvar_g_rm_damage, autocvar_g_rm_edgedamage, autocvar_g_rm_radius, world, world, autocvar_g_rm_force, WEP_DEVASTATOR.m_id | HITTYPE_SPLASH, other); + remove(dmgent); +} + void W_Vaporizer_Attack(void) { float flying; @@ -115,13 +131,147 @@ void W_Vaporizer_Attack(void) break; } + if(autocvar_g_rm) + if(!(trace_dphitq3surfaceflags & (Q3SURFACEFLAG_SKY | Q3SURFACEFLAG_NOIMPACT))) + W_RocketMinsta_Explosion(trace_endpos); + W_DecreaseAmmo(((g_instagib) ? 1 : WEP_CVAR_PRI(vaporizer, ammo))); } +void W_RocketMinsta_Laser_Explode (void) +{ + if(other.takedamage == DAMAGE_AIM) + if(IS_PLAYER(other)) + if(DIFF_TEAM(self.realowner, other)) + if(other.deadflag == DEAD_NO) + if(IsFlying(other)) + Send_Notification(NOTIF_ONE, self.realowner, MSG_ANNCE, ANNCE_ACHIEVEMENT_ELECTROBITCH); + + self.event_damage = func_null; + self.takedamage = DAMAGE_NO; + RadiusDamage (self, self.realowner, self.rm_damage, self.rm_edmg, autocvar_g_rm_laser_radius, world, world, self.rm_force, self.projectiledeathtype, other); + remove(self); +} + +void W_RocketMinsta_Laser_Touch (void) +{ + PROJECTILE_TOUCH; + //W_RocketMinsta_Laser_Explode (); + RadiusDamage (self, self.realowner, self.rm_damage, self.rm_edmg, autocvar_g_rm_laser_radius, world, world, self.rm_force, self.projectiledeathtype, other); + remove(self); +} + +void W_RocketMinsta_Attack2(void) +{ + makevectors(self.v_angle); + + entity proj; + float counter = 0; + float total = autocvar_g_rm_laser_count; + float spread = autocvar_g_rm_laser_spread; + float rndspread = autocvar_g_rm_laser_spread_random; + + float w = self.weapon; + self.weapon = WEP_ELECTRO.m_id; + W_SetupShot_ProjectileSize (self, '0 0 -3', '0 0 -3', false, 2, W_Sound("crylink_fire"), CH_WEAPON_A, autocvar_g_rm_laser_damage); + self.weapon = w; + + Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + while(counter < total) + { + proj = spawn (); + proj.classname = "plasma_prim"; + proj.owner = proj.realowner = self; + proj.bot_dodge = true; + proj.bot_dodgerating = autocvar_g_rm_laser_damage; + proj.use = W_RocketMinsta_Laser_Explode; + proj.think = adaptor_think2use_hittype_splash; + proj.nextthink = time + autocvar_g_rm_laser_lifetime; + PROJECTILE_MAKETRIGGER(proj); + proj.projectiledeathtype = WEP_ELECTRO.m_id; + setorigin(proj, w_shotorg); + + proj.rm_force = autocvar_g_rm_laser_force / total; + proj.rm_damage = autocvar_g_rm_laser_damage / total; + proj.rm_edmg = proj.rm_damage; + + //W_SetupProjectileVelocity(proj, autocvar_g_rm_laser_speed, spread * (rndspread ? random() : 1) * autocvar_g_rm_laser_speed); + + proj.movetype = MOVETYPE_BOUNCEMISSILE; + //W_SETUPPROJECTILEVELOCITY(proj, g_balance_minstanex_laser); + proj.velocity = (w_shotdir + (((counter + 0.5) / total) * 2 - 1) * v_right * (spread * (rndspread ? random() : 1))) * cvar("g_rm_laser_speed"); + proj.velocity_z = proj.velocity_z + cvar("g_rm_laser_zspread") * (random() - 0.5); + proj.velocity = W_CalculateProjectileVelocity(proj.realowner.velocity, proj.velocity, true); + proj.angles = vectoangles(proj.velocity); + proj.touch = W_RocketMinsta_Laser_Touch; + setsize(proj, '0 0 -3', '0 0 -3'); + proj.flags = FL_PROJECTILE; + proj.missile_flags = MIF_SPLASH; + + CSQCProjectile(proj, true, PROJECTILE_ROCKETMINSTA_LASER, true); + + MUTATOR_CALLHOOK(EditProjectile, self, proj); + counter++; + } +} + +void W_RocketMinsta_Attack3 (void) +{ + makevectors(self.v_angle); + + entity proj; + float counter = 0; + float total = 1; + + int w = self.weapon; + self.weapon = WEP_ELECTRO.m_id; + W_SetupShot_ProjectileSize (self, '0 0 -3', '0 0 -3', false, 2, W_Sound("electro_fire2"), CH_WEAPON_A, autocvar_g_rm_laser_damage); + self.weapon = w; + + Send_Effect(EFFECT_ELECTRO_MUZZLEFLASH, w_shotorg, w_shotdir * 1000, 1); + + while(counter < total) + { + proj = spawn (); + proj.classname = "plasma_prim"; + proj.owner = proj.realowner = self; + proj.bot_dodge = true; + proj.bot_dodgerating = autocvar_g_rm_laser_damage; + proj.use = W_RocketMinsta_Laser_Explode; + proj.think = adaptor_think2use_hittype_splash; + proj.nextthink = time + autocvar_g_rm_laser_lifetime; + PROJECTILE_MAKETRIGGER(proj); + proj.projectiledeathtype = WEP_ELECTRO.m_id; + setorigin(proj, w_shotorg); + + proj.rm_force = autocvar_g_rm_laser_force / total; + proj.rm_damage = autocvar_g_rm_laser_damage / total; + proj.rm_edmg = proj.rm_damage; + + //W_SetupProjectileVelocity(proj, autocvar_g_rm_laser_speed, spread * (rndspread ? random() : 1) * autocvar_g_rm_laser_speed); + + proj.movetype = MOVETYPE_BOUNCEMISSILE; + proj.velocity = w_shotdir * autocvar_g_rm_laser_speed; + proj.velocity = W_CalculateProjectileVelocity(proj.realowner.velocity, proj.velocity, true); + proj.angles = vectoangles(proj.velocity); + proj.touch = W_RocketMinsta_Laser_Touch; + setsize(proj, '0 0 -3', '0 0 -3'); + proj.flags = FL_PROJECTILE; + proj.missile_flags = MIF_SPLASH; + + CSQCProjectile(proj, true, PROJECTILE_ROCKETMINSTA_LASER, true); + + MUTATOR_CALLHOOK(EditProjectile, self, proj); + counter++; + } +} + float W_Vaporizer(float req) { float ammo_amount; float vaporizer_ammo; + float rapid = autocvar_g_rm_laser_rapid; // now multiple WR_s use this vaporizer_ammo = ((g_instagib) ? 1 : WEP_CVAR_PRI(vaporizer, ammo)); @@ -144,7 +294,7 @@ float W_Vaporizer(float req) WEP_ACTION(self.weapon, WR_RELOAD); else if(WEP_CVAR(vaporizer, reload_ammo) && self.clip_load < vaporizer_ammo) // forced reload WEP_ACTION(self.weapon, WR_RELOAD); - else if(self.BUTTON_ATCK) + if(self.BUTTON_ATCK && (self.ammo_cells || !autocvar_g_rm) && !forbidWeaponUse(self)) { if(weapon_prepareattack(0, WEP_CVAR_PRI(vaporizer, refire))) { @@ -152,10 +302,28 @@ float W_Vaporizer(float req) weapon_thinkf(WFRAME_FIRE1, WEP_CVAR_PRI(vaporizer, animtime), w_ready); } } - else if(self.BUTTON_ATCK2) + if(self.BUTTON_ATCK2 || (self.BUTTON_ATCK && !self.ammo_cells && autocvar_g_rm)) { - if(self.jump_interval <= time) - if(weapon_prepareattack(1, -1)) + if((autocvar_g_rm && autocvar_g_rm_laser) || autocvar_g_rm_laser == 2) + { + if(self.jump_interval <= time && !self.held_down) + { + if(rapid) + self.held_down = true; + self.jump_interval = time + autocvar_g_rm_laser_refire; + self.jump_interval2 = time + autocvar_g_rm_laser_rapid_delay; + damage_goodhits = 0; + W_RocketMinsta_Attack2(); + } + else if(rapid && self.jump_interval2 <= time && self.held_down) + { + self.jump_interval2 = time + autocvar_g_rm_laser_rapid_refire; + damage_goodhits = 0; + W_RocketMinsta_Attack3(); + //weapon_thinkf(WFRAME_FIRE2, autocvar_g_rm_laser_rapid_animtime, w_ready); + } + } + else if (self.jump_interval <= time) { // handle refire manually, so that primary and secondary can be fired without conflictions (important for instagib) self.jump_interval = time + WEP_CVAR_SEC(vaporizer, refire) * W_WeaponRateFactor(); @@ -165,6 +333,7 @@ float W_Vaporizer(float req) W_DecreaseAmmo(WEP_CVAR_SEC(vaporizer, ammo)); // ugly instagib hack to reuse the fire mode of the laser + makevectors(self.v_angle); int oldwep = self.weapon; // we can't avoid this hack self.weapon = WEP_BLASTER.m_id; W_Blaster_Attack( @@ -185,7 +354,9 @@ float W_Vaporizer(float req) weapon_thinkf(WFRAME_FIRE2, WEP_CVAR_SEC(vaporizer, animtime), w_ready); } } - + else + self.held_down = false; + return true; } case WR_INIT: @@ -262,8 +433,7 @@ float W_Vaporizer(float req) { case WR_IMPACTEFFECT: { - vector org2; - org2 = w_org + w_backoff * 6; + vector org2 = w_org + w_backoff * 6; if(w_deathtype & HITTYPE_SECONDARY) { pointparticles(particleeffectnum(EFFECT_BLASTER_IMPACT), org2, w_backoff * 1000, 1); diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh index f6ac64efe..45de5e3e8 100644 --- a/qcsrc/server/autocvars.qh +++ b/qcsrc/server/autocvars.qh @@ -867,4 +867,22 @@ float autocvar_g_buffs_inferno_damagemultiplier; float autocvar_g_buffs_swapper_range; float autocvar_g_buffs_magnet_range_item; float autocvar_sv_player_scale; +float autocvar_g_rm; +float autocvar_g_rm_damage; +float autocvar_g_rm_edgedamage; +float autocvar_g_rm_force; +float autocvar_g_rm_radius; +float autocvar_g_rm_laser; +float autocvar_g_rm_laser_count; +float autocvar_g_rm_laser_speed; +float autocvar_g_rm_laser_spread; +float autocvar_g_rm_laser_spread_random; +float autocvar_g_rm_laser_lifetime; +float autocvar_g_rm_laser_damage; +float autocvar_g_rm_laser_refire; +float autocvar_g_rm_laser_rapid; +float autocvar_g_rm_laser_rapid_refire; +float autocvar_g_rm_laser_rapid_delay; +float autocvar_g_rm_laser_radius; +float autocvar_g_rm_laser_force; #endif diff --git a/qcsrc/server/g_tetris.qc b/qcsrc/server/g_tetris.qc deleted file mode 100644 index 54577eca7..000000000 --- a/qcsrc/server/g_tetris.qc +++ /dev/null @@ -1,1264 +0,0 @@ -#if defined(CSQC) -#elif defined(MENUQC) -#elif defined(SVQC) - #include "../dpdefs/dpextensions.qh" - #include "autocvars.qh" -#endif - -/* - -Installation: - -compile with -DTETRIS - -*/ - -#ifdef TETRIS - -.vector tet_org; - -float tet_vs_current_id; -float tet_vs_current_timeout; -.float tet_vs_id, tet_vs_addlines; -.float tet_highest_line; -.float tetris_on, tet_gameovertime, tet_drawtime, tet_autodown; -.vector piece_pos; -.float piece_type, next_piece, tet_score, tet_lines; -.float tet_piece_bucket; - -// tetris_on states: -// 1 = running -// 2 = game over -// 3 = waiting for VS players - -float tet_high_score = 0; - -const vector TET_START_PIECE_POS = '5 1 0'; -const float TET_LINES = 22; -const float TET_DISPLAY_LINES = 20; -const float TET_WIDTH = 10; -const string TET_EMPTY_LINE = "0000000000"; // must match TET_WIDTH -//character values -const float TET_BORDER = 139; -const float TET_BLOCK = 133; -const float TET_SPACE = 160; // blankness - - - -const float TETKEY_UP = 1; -const float TETKEY_DOWN = 2; -const float TETKEY_LEFT = 4; -const float TETKEY_RIGHT = 8; -const float TETKEY_ROTLEFT = 16; -const float TETKEY_ROTRIGHT = 32; -const float TETKEY_DROP = 64; -const string TET_PADDING_RIGHT = "\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0\xA0"; // get away from crosshair - -const float PIECES = 7; - -float tet_line_buf; - -float SVC_CENTERPRINTa = 26; - -float Tetris_Level() -{ - return ((floor((self.tet_lines / 10)) + 1)); -} - -void tetsnd(string snd) -{ - play2(self, strcat("sounds/tetris/", snd)); -} - -/* -********************************* - -Library Functions - -********************************* -*/ -void SetLine(float ln, string vl) -{ - if(ln < 1 || ln > TET_LINES) - error("WTF"); - bufstr_set(tet_line_buf, ln + TET_LINES * num_for_edict(self), vl); -} - -string GetLine(float ln) -{ - if(ln < 1 || ln > TET_LINES) - error("WTF"); - if(ln < 1 || ln > TET_LINES) - return TET_EMPTY_LINE; - return bufstr_get(tet_line_buf, ln + TET_LINES * num_for_edict(self)); -} - -float GetXBlock(float x, string dat) -{ - if(x < 1 || x > TET_WIDTH) - error("WTF"); - return stof(substring(dat, x-1, 1)); -} - -string SetXBlock(float x, string dat, float val) -{ - return strcat( - substring(dat, 0, x-1), - ftos(val), - substring(dat, x, -1) - ); -} - - -float GetSquare(float x, float y) -{ - return GetXBlock(x, GetLine(y)); -} - -void SetSquare(float x, float y, float val) -{ - string dat; - dat = GetLine(y); - dat = SetXBlock(x, dat, val); - SetLine(y, dat); -} - -float PieceColor(float pc) -{ - if (pc == 1) - return 3; // O - else if (pc == 2) - return 4; // J - else if (pc == 3) - return 7; // L // we don't have orange, let's use white instead! - else if (pc == 4) - return 5; // I - else if (pc == 5) - return 1; // Z - else if (pc == 6) - return 2; // S - else if (pc == 7) - return 6; // T - else - return 0; -} -vector PieceShape(float pc) -{ - if (pc == 1) - return '20 20 0'; // O - else if (pc == 2) - return '1 21 0'; // J - else if (pc == 3) - return '16 21 0'; // L - else if (pc == 4) - return '0 85 0'; // I - else if (pc == 5) - return '5 20 0'; // Z - else if (pc == 6) - return '20 5 0'; // S - else if (pc == 7) - return '4 21 0'; // T - else - return '0 0 0'; -} -vector PieceSize(float pc) -{ - if (pc == 1) - return '2 2 0'; // O - else if (pc == 2) - return '3 2 0'; // J - else if (pc == 3) - return '3 2 0'; // L - else if (pc == 4) - return '4 1 0'; // I - else if (pc == 5) - return '3 2 0'; // Z - else if (pc == 6) - return '3 2 0'; // S - else if (pc == 7) - return '3 2 0'; // T - else - return '0 0 0'; -} -vector PieceCenter(float pc) -{ - if(pc == 1) - return '2.5 1.5 0'; // O - else if (pc == 2) - return '2 2 0'; // J - else if (pc == 3) - return '2 2 0'; // L - else if (pc == 4) - return '2.5 2.5 0'; // I - else if (pc == 5) - return '2 2 0'; // Z - else if (pc == 6) - return '2 2 0'; // S - else if (pc == 7) - return '2 2 0'; // T - else - return '0 0 0'; -} - -// do x 1..4 and y 1..4 in case of rotation -float PieceMetric(float x, float y, float rot, float pc) -{ - float t; - vector ce; - - // return bits of a piece - ce = PieceCenter(pc); - if (rot == 1) // 90 degrees - { - // x+cx, y+cy -> -y+cx, x+cy - // X, Y -> -Y+cy+cx, X-cx+cy - // x = X-cx - // y = Y-cy - t = y; - y = x - ce.x + ce.y; - x = -t + ce.x + ce.y; - } - else if (rot == 2)//180 - { - x = 2 * ce.x - x; - y = 2 * ce.y - y; - } - else if (rot == 3) // 270 - { - // x+cx, y+cy -> y+cx, -x+cy - // X, Y -> Y-cy+cx, -X+cx+cy - // x = X-cx - // y = Y-cy - t = y; - y = -x + ce.y + ce.x; - x = t - ce.y + ce.x; - } - if (x < 1 || y < 1 || x > 4 || y > 2) - return 0; - ce = PieceShape(pc); - if (y == 1) - return !!(ce.x & pow(4, x-1)); // first row - else if (y == 2) - return !!(ce.y & pow(4, x-1)); // second row - else - return 0; // illegal parms -} -vector tet_piecemins; -vector tet_piecemaxs; -void PieceMinsMaxs(float rot, float pc) -{ - vector sz, ce; - float t; - vector v; - - sz = PieceSize(pc); - ce = PieceCenter(pc); - // 1 = 2..2 - // 2 = 2..3 - // 3 = 1..3 - // 4 = 1..4 - tet_piecemins.x = floor(3.0 - sz.x * 0.5); - tet_piecemaxs.x = floor(2.0 + sz.x * 0.5); - if(sz.y == 1) - { - // special case for "I" - tet_piecemins.y = tet_piecemaxs.y = 2; - } - else - { - tet_piecemins.y = 1; - tet_piecemaxs.y = sz.y; - } - //printf("ce%v sz%v mi%v ma%v\n", ce, sz, tet_piecemins, tet_piecemaxs); - if (rot == 1) // 90 degrees - { - t = tet_piecemins.y; - tet_piecemins.y = -tet_piecemins.x + ce.y + ce.x; - tet_piecemins.x = t - ce.y + ce.x; - t = tet_piecemaxs.y; - tet_piecemaxs.y = -tet_piecemaxs.x + ce.y + ce.x; - tet_piecemaxs.x = t - ce.y + ce.x; - // swap mins_y, maxs_y - t = tet_piecemins.y; - tet_piecemins.y = tet_piecemaxs.y; - tet_piecemaxs.y = t; - // TODO OPTIMIZE - } - else if (rot == 2)//180 - { - v = tet_piecemins; - tet_piecemins = 2 * ce - tet_piecemaxs; - tet_piecemaxs = 2 * ce - v; - } - else if (rot == 3) // 270 - { - t = tet_piecemins.y; - tet_piecemins.y = tet_piecemins.x - ce.x + ce.y; - tet_piecemins.x = -t + ce.x + ce.y; - t = tet_piecemaxs.y; - tet_piecemaxs.y = tet_piecemaxs.x - ce.x + ce.y; - tet_piecemaxs.x = -t + ce.x + ce.y; - // swap mins_x, maxs_x - t = tet_piecemins.x; - tet_piecemins.x = tet_piecemaxs.x; - tet_piecemaxs.x = t; - // TODO OPTIMIZE - } -#ifdef VERIFY - LOG_INFO(vtos(tet_piecemins), "-"); - LOG_INFO(vtos(tet_piecemaxs), "\n"); - if(tet_piecemins.x > tet_piecemaxs.x) - error("inconsistent mins/maxs"); - if(tet_piecemins.y > tet_piecemaxs.y) - error("inconsistent mins/maxs"); - float i, j; - vector realmins, realmaxs; - realmins = '4 4 0'; - realmaxs = '1 1 0'; - for(i = 1; i <= 4; ++i) - for(j = 1; j <= 4; ++j) - if(PieceMetric(i, j, rot, pc)) - { - realmins.x = min(realmins.x, i); - realmins.y = min(realmins.y, j); - realmaxs.x = max(realmaxs.x, i); - realmaxs.y = max(realmaxs.y, j); - } - if(realmins != tet_piecemins || realmaxs != tet_piecemaxs) - error(sprintf("incorrect mins/maxs: %v %v in %d rot %d mins %v maxs %v\n", realmins, realmaxs, rot, pc, tet_piecemins, tet_piecemaxs)); -#endif -} -/* -********************************* - -Draw - -********************************* -*/ - - -/* some prydon gate functions to make life easier.... - -somewhat modified because we don't need all the fanciness Prydon Gate is capable of - -*/ - -void WriteTetrisString(string s) -{ - WriteUnterminatedString(MSG_ONE, strconv(0, 2, 2, s)); -} - -float pnum(float num, float dig) -{ - float f, i; - if (num < 0) - { - WriteChar(MSG_ONE, 173); - num = 0 - num; - } - f = floor(num / 10); - num = num - (f * 10); - if (f) - dig = pnum(f, dig+1); - else - { - // pad to 6 - for (i = 0; i < (5 - dig); i = i + 1) - WriteChar(MSG_ONE, TET_SPACE); - } - WriteChar(MSG_ONE, 176 + num); - return dig; -} - -void DrawLine(float ln) -{ - float x, d; - WriteChar(MSG_ONE, TET_BORDER); - - for (x = 1; x <= TET_WIDTH; x = x + 1) - { - d = GetSquare(x, ln + TET_LINES - TET_DISPLAY_LINES); - if (d) - { - WriteChar(MSG_ONE, '^'); - WriteChar(MSG_ONE, d + '0'); - WriteChar(MSG_ONE, TET_BLOCK); - } - else - WriteChar(MSG_ONE, TET_SPACE); - } - WriteChar(MSG_ONE, '^'); - WriteChar(MSG_ONE, '7'); - WriteChar(MSG_ONE, TET_BORDER); -} - -void DrawPiece(float pc, float ln) -{ - float x, d, piece_ln, pcolor; - vector piece_dat; - pcolor = PieceColor(pc); - WriteChar(MSG_ONE, TET_SPACE); // pad to 6 - - piece_dat = PieceShape(pc); - if (ln == 1) - piece_ln = piece_dat.x; - else - piece_ln = piece_dat.y; - for (x = 1; x <= 4; x = x + 1) - { - if (piece_ln & pow(4, x-1)) - { - WriteChar(MSG_ONE, '^'); - WriteChar(MSG_ONE, pcolor + '0'); - WriteChar(MSG_ONE, TET_BLOCK); - } - else - WriteChar(MSG_ONE, TET_SPACE); - } - WriteChar(MSG_ONE, TET_SPACE); // pad to 6 -} -void Draw_Tetris() -{ - float i; - entity head; - msg_entity = self; - WriteChar(MSG_ONE, SVC_CENTERPRINTa); - if(autocvar_g_bastet) - { - WriteTetrisString("NEVER GONNA GIVE YOU"); - WriteChar(MSG_ONE, 10); - } - // decoration - for (i = 1; i <= (TET_WIDTH + 2); i = i + 1) - WriteChar(MSG_ONE, TET_BORDER); - WriteTetrisString(" "); - WriteUnterminatedString(MSG_ONE, TET_PADDING_RIGHT); - WriteChar(MSG_ONE, 10); - for (i = 1; i <= TET_DISPLAY_LINES; i = i + 1) - { - if(self.tetris_on == 2) - WriteTetrisString(" GAME OVER "); - else if(self.tetris_on == 3) - WriteTetrisString("PLEASE WAIT"); - else - DrawLine(i); - if (i == 1) - WriteTetrisString(autocvar_g_bastet ? " THAT " : " NEXT "); - else if (i == 3) - DrawPiece(self.next_piece, 1); - else if (i == 4) - DrawPiece(self.next_piece, 2); - else if (i == 6) - WriteTetrisString(" LINES"); - else if (i == 7) - pnum(self.tet_lines, 0); - else if (i == 9) - WriteTetrisString(" SCORE"); - else if (i == 10) - pnum(self.tet_score, 0); - else if (i == 12) - WriteTetrisString(" HIGH "); - else if (i == 13) - WriteTetrisString(" SCORE"); - else if (i == 14) - pnum(tet_high_score, 0); - else if (i == 16) - WriteTetrisString(" LEVEL"); - else if (i == 17) - pnum(Tetris_Level(), 0); - else - WriteTetrisString(" "); - WriteUnterminatedString(MSG_ONE, TET_PADDING_RIGHT); - WriteChar(MSG_ONE, 10); - } - // decoration - - for (i = 1; i <= (TET_WIDTH + 2); i = i + 1) - WriteChar(MSG_ONE, TET_BORDER); - WriteTetrisString(" "); - WriteUnterminatedString(MSG_ONE, TET_PADDING_RIGHT); - WriteChar(MSG_ONE, 10); - - // VS game status - if(self.tet_vs_id) - { - WriteChar(MSG_ONE, 10); - WriteChar(MSG_ONE, 10); - if(self.tetris_on == 3) - { - WriteUnterminatedString(MSG_ONE, strcat("WAITING FOR OTHERS (", ftos(ceil(tet_vs_current_timeout - time)), " SEC)\n")); - } - - WriteChar(MSG_ONE, 10); - FOR_EACH_REALCLIENT(head) if(head.tetris_on) if(head.tet_vs_id == self.tet_vs_id) - { - if(head == self) - WriteUnterminatedString(MSG_ONE, ">"); - else - WriteUnterminatedString(MSG_ONE, " "); - if(head.tetris_on == 2) - WriteUnterminatedString(MSG_ONE, " X_X"); - else - pnum(head.tet_highest_line, 0); - WriteUnterminatedString(MSG_ONE, " "); - WriteUnterminatedString(MSG_ONE, head.netname); - WriteChar(MSG_ONE, 10); - } - } - - WriteChar(MSG_ONE, 0); -} -/* -********************************* - -Game Functions - -********************************* -*/ - -// reset the game -void ResetTetris() -{ - float i; - - if(!tet_line_buf) - tet_line_buf = buf_create(); - - for (i=1; i<=TET_LINES; i = i + 1) - SetLine(i, TET_EMPTY_LINE); - self.piece_pos = '0 0 0'; - self.piece_type = 0; - self.next_piece = self.tet_lines = self.tet_score = 0; - self.tet_piece_bucket = 0; -} - -void Tet_GameExit() -{ - centerprint(self, " "); - self.tetris_on = 0; - self.tet_vs_id = 0; - ResetTetris(); - self.movetype = MOVETYPE_WALK; -} - -void PrintField() -{ - string l; - float r, c; - for(r = 1; r <= TET_LINES; ++r) - { - l = GetLine(r); - LOG_INFO(">"); - for(c = 1; c <= TET_WIDTH; ++c) - { - LOG_INFO(ftos(GetXBlock(c, l))); - } - LOG_INFO("\n"); - } -} - -float BastetEvaluate() -{ - float height; - string l; - float lines; - float score, score_save; - string occupied, occupied_save; - float occupied_count, occupied_count_save; - float i, j, line; - - score = 0; - - // adds a bonus for each free dot above the occupied blocks profile - occupied = TET_EMPTY_LINE; - occupied_count = TET_WIDTH; - height = 0; - lines = 0; - for(i = 1; i <= TET_LINES; ++i) - { - l = GetLine(i); - if(l == TET_EMPTY_LINE) - { - height = i; - continue; - } - line = 1; - occupied_save = occupied; - occupied_count_save = occupied_count; - score_save = score; - for(j = 1; j <= TET_WIDTH; ++j) - { - if(GetXBlock(j, l)) - { - if(!GetXBlock(j, occupied)) - { - occupied = SetXBlock(j, occupied, 1); - --occupied_count; - } - } - else - line = 0; - score += 10000 * occupied_count; - } - if(line) - { - occupied = occupied_save; - occupied_count = occupied_count_save; - score = score_save + 100000000 + 10000 * TET_WIDTH + 1000; - ++lines; - } - } - - score += 1000 * height; - - return score; -} - -float CheckMetrics(float piece, float orgx, float orgy, float rot); -void ClearPiece(float piece, float orgx, float orgy, float rot); -void CementPiece(float piece, float orgx, float orgy, float rot); -float bastet_profile_evaluate_time; -float bastet_profile_checkmetrics_time; -float BastetSearch(float buf, float pc, float x, float y, float rot, float move_bias) -// returns best score, or -1 if position is impossible -{ - string r; - float b; - float s, sm; - float t1, t2; - - if(move_bias < 0) - return 0; // DO NOT WANT - - if(x < 1 || x > TET_WIDTH || y < 1 || y > TET_LINES) - return -1; // impossible - if(rot < 0) rot = 3; - if(rot > 3) rot = 0; - - // did we already try? - b = x + (TET_WIDTH+2) * (y + (TET_LINES+2) * rot); - r = bufstr_get(buf, b); - if(r != "") // already tried - return stof(r); - - bufstr_set(buf, b, "0"); // in case we READ that, not that bad - we already got that value in another branch then anyway - - - - t1 = gettime(GETTIME_HIRES); - if(CheckMetrics(pc, x, y, rot)) - { - t2 = gettime(GETTIME_HIRES); - bastet_profile_checkmetrics_time += (t2 - t1); - // try all moves - sm = 1; - s = BastetSearch(buf, pc, x-1, y, rot, move_bias - 1); if(s > sm) sm = s; - s = BastetSearch(buf, pc, x+1, y, rot, move_bias - 1); if(s > sm) sm = s; - s = BastetSearch(buf, pc, x, y, rot+1, move_bias - 1); if(s > sm) sm = s; - s = BastetSearch(buf, pc, x, y, rot-1, move_bias - 1); if(s > sm) sm = s; - - s = BastetSearch(buf, pc, x, y+1, rot, move_bias + 2); if(s > sm) sm = s; - if(s < 0) - { - //printf("MAY CEMENT AT: %d %d %d\n", x, y, rot); - // moving down did not work - that means we can fixate the block here - t1 = gettime(GETTIME_HIRES); - - CementPiece(pc, x, y, rot); - s = BastetEvaluate(); - ClearPiece(pc, x, y, rot); - - t2 = gettime(GETTIME_HIRES); - bastet_profile_evaluate_time += (t2 - t1); - - if(s > sm) sm = s; - } - } - else - { - t2 = gettime(GETTIME_HIRES); - bastet_profile_checkmetrics_time += (t2 - t1); - sm = -1; // impassible - } - - bufstr_set(buf, b, ftos(sm)); - - return sm; -} - -float bastet_piece[7]; -float bastet_score[7]; -float bastet_piecetime[7]; -float BastetPiece() -{ - float b; - - bastet_profile_evaluate_time = 0; - bastet_profile_checkmetrics_time = 0; - float t1 = gettime(GETTIME_HIRES); - - b = buf_create(); bastet_piece[0] = 1; bastet_score[0] = BastetSearch(b, 1, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[0]; buf_del(b); - b = buf_create(); bastet_piece[1] = 2; bastet_score[1] = BastetSearch(b, 2, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[1]; buf_del(b); - b = buf_create(); bastet_piece[2] = 3; bastet_score[2] = BastetSearch(b, 3, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[2]; buf_del(b); - b = buf_create(); bastet_piece[3] = 4; bastet_score[3] = BastetSearch(b, 4, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[3]; buf_del(b); - b = buf_create(); bastet_piece[4] = 5; bastet_score[4] = BastetSearch(b, 5, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[4]; buf_del(b); - b = buf_create(); bastet_piece[5] = 6; bastet_score[5] = BastetSearch(b, 6, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[5]; buf_del(b); - b = buf_create(); bastet_piece[6] = 7; bastet_score[6] = BastetSearch(b, 7, TET_START_PIECE_POS_x, 1+TET_START_PIECE_POS_y, TET_START_PIECE_POS_y, TET_WIDTH) + 100 * random() + bastet_piecetime[6]; buf_del(b); - - float t2 = gettime(GETTIME_HIRES); - LOG_TRACEF("Time taken: %.6f seconds (of this, ev = %.2f%%, cm = %.2f%%)\n", t2 - t1, 100 * bastet_profile_evaluate_time / (t2 - t1), 100 * bastet_profile_checkmetrics_time / (t2 - t1)); - - // sort - float i, j, k, p, s; - -/* - for(i = 0; i < 7; ++i) - { - printf("piece %s value = %d\n", substring("OJLIZST", bastet_piece[i]-1, 1), bastet_score[i]); - } -*/ - - for(i = 0; i < 7; ++i) - { - k = i; - p = bastet_piece[k]; - s = bastet_score[k]; - for(j = i + 1; j < 7; ++j) - { - if(bastet_score[j] < s) - { - k = j; - s = bastet_score[k]; - p = bastet_piece[k]; - } - } - if(k != i) - { - bastet_score[k] = bastet_score[i]; - bastet_piece[k] = bastet_piece[i]; - bastet_score[i] = s; - bastet_piece[i] = p; - } - } - - b = random(); - if(b < 0.8) - j = 0; - else if(b < 0.92) - j = 1; - else if(b < 0.98) - j = 2; - else - j = 3; - j = bastet_piece[j]; - - for(i = 0; i < 7; ++i) - { - if(i == j-1) - bastet_piecetime[i] = 0.2 * bastet_piecetime[i]; - else - bastet_piecetime[i] = 1.8 * bastet_piecetime[i] + 1000; - } - - return j; -} - - -/* -********************************* - -Game Mechanics - -********************************* -*/ -.float tet_piece_bucket; -float RandomPiece() -{ - float i, j; - float p, q; - float b; - float seen; - - if(self.tet_piece_bucket > 1) - { - p = mod(self.tet_piece_bucket, 7); - self.tet_piece_bucket = floor(self.tet_piece_bucket / 7); - return p + 1; - } - else - { - p = floor(random() * 7); - seen = pow(2, p); - b = 1; - for(i = 6; i > 0; --i) - { - q = floor(random() * i); - for(j = 0; j <= q; ++j) - if(seen & pow(2, j)) - ++q; - if(seen & pow(2, q)) - error("foo 1"); - if(q >= 7) - error("foo 2"); - seen |= pow(2, q); - b *= 7; - b += q; - } - self.tet_piece_bucket = b; - return p + 1; - } -} - -void TetAddScore(float n) -{ - self.tet_score = self.tet_score + n * Tetris_Level(); - if (self.tet_score > tet_high_score) - tet_high_score = self.tet_score; -} -float CheckMetrics(float piece, float orgx, float orgy, float rot) /*FIXDECL*/ -{ - // check to see if the piece, if moved to the locations will overlap - - float x, y; - string l; - // why did I start counting from 1, damnit - orgx = orgx - 1; - orgy = orgy - 1; - - PieceMinsMaxs(rot, piece); - if (tet_piecemins.x+orgx<1 || tet_piecemaxs.x+orgx > TET_WIDTH || tet_piecemins.y+orgy<1 || tet_piecemaxs.y+orgy> TET_LINES) - return false; // ouside the level - for (y = tet_piecemins.y; y <= tet_piecemaxs.y; y = y + 1) - { - l = GetLine(y + orgy); - if(l != TET_EMPTY_LINE) - for (x = tet_piecemins.x; x <= tet_piecemaxs.x; x = x + 1) - if (PieceMetric(x, y, rot, piece)) - if (GetXBlock(x + orgx, l)) - return false; // uhoh, gonna hit something. - } - return true; -} - -void ClearPiece(float piece, float orgx, float orgy, float rot) /*FIXDECL*/ -{ - float x, y; - // why did I start counting from 1, damnit - orgx = orgx - 1; - orgy = orgy - 1; - - PieceMinsMaxs(rot, piece); - for (y = tet_piecemins.y; y <= tet_piecemaxs.y; y = y + 1) - { - for (x = tet_piecemins.x; x <= tet_piecemaxs.x; x = x + 1) - { - if (PieceMetric(x, y, rot, piece)) - { - SetSquare(x + orgx, y + orgy, 0); - } - } - } -} -void CementPiece(float piece, float orgx, float orgy, float rot) /*FIXDECL*/ -{ - float pcolor; - float x, y; - // why did I start counting from 1, damnit - orgx = orgx - 1; - orgy = orgy - 1; - - pcolor = PieceColor(piece); - - PieceMinsMaxs(rot, piece); - for (y = tet_piecemins.y; y <= tet_piecemaxs.y; y = y + 1) - { - for (x = tet_piecemins.x; x <= tet_piecemaxs.x; x = x + 1) - { - if (PieceMetric(x, y, rot, piece)) - { - SetSquare(x + orgx, y + orgy, pcolor); - } - } - } -} - -const float LINE_LOW = 349525; -const float LINE_HIGH = 699050; // above number times 2 - -void AddLines(float n) -{ - entity head; - if(!self.tet_vs_id) - return; - FOR_EACH_REALCLIENT(head) if(head != self) if(head.tetris_on) if(head.tet_vs_id == self.tet_vs_id) - head.tet_vs_addlines += n; -} - -void CompletedLines() -{ - float y, cleared, added, pos, i; - string ln; - - cleared = 0; - y = TET_LINES; - for (;;) - { - ln = GetLine(y); - if(strstrofs(ln, "0", 0) < 0) - cleared = cleared + 1; - else - y = y - 1; - if(y < 1) - break; - if(y - cleared < 1) - ln = TET_EMPTY_LINE; - else - ln = GetLine(y - cleared); - SetLine(y, ln); - } - - if(cleared >= 4) - AddLines(cleared); - else if(cleared >= 1) - AddLines(cleared - 1); - - self.tet_lines = self.tet_lines + cleared; - TetAddScore(cleared * cleared * 10); - - added = self.tet_vs_addlines; - self.tet_vs_addlines = 0; - - if(added) - { - for(y = 1; y <= TET_LINES - added; ++y) - { - SetLine(y, GetLine(y + added)); - } - for(y = max(1, TET_LINES - added + 1); y <= TET_LINES; ++y) - { - pos = floor(random() * TET_WIDTH); - ln = TET_EMPTY_LINE; - for(i = 1; i <= TET_WIDTH; ++i) - if(i != pos) - ln = SetXBlock(i, ln, floor(random() * 7 + 1)); - SetLine(y, ln); - } - } - - self.tet_highest_line = 0; - for(y = 1; y <= TET_LINES; ++y) - if(GetLine(y) != TET_EMPTY_LINE) - { - self.tet_highest_line = TET_LINES + 1 - y; - break; - } - - if(added) - tetsnd("tetadd"); - else if(cleared >= 4) - tetsnd("tetris"); - else if(cleared) - tetsnd("tetline"); - else - tetsnd("tetland"); -} - -void HandleGame(float keyss) -{ - - // first off, we need to see if we need a new piece - vector piece_data; - vector check_pos; - vector old_pos; - float brand_new; - float i; - brand_new = 0; - - - if (self.piece_type == 0) - { - self.piece_pos = TET_START_PIECE_POS; // that's about middle top, we count from 1 ARGH - - if(autocvar_g_bastet) - { - self.piece_type = BastetPiece(); - self.next_piece = bastet_piece[6]; - } - else - { - if (self.next_piece) - self.piece_type = self.next_piece; - else - self.piece_type = RandomPiece(); - self.next_piece = RandomPiece(); - } - keyss = 0; // no movement first frame - self.tet_autodown = time + 0.2; - brand_new = 1; - } - else - ClearPiece(self.piece_type, self.piece_pos.x, self.piece_pos.y, self.piece_pos.z); - - // next we need to check the piece metrics against what's on the level - // based on the key order - - old_pos = check_pos = self.piece_pos; - - float nudge; - nudge = 0; - if (keyss & TETKEY_RIGHT) - { - check_pos.x = check_pos.x + 1; - tetsnd("tetmove"); - } - else if (keyss & TETKEY_LEFT) - { - check_pos.x = check_pos.x - 1; - tetsnd("tetmove"); - } - else if (keyss & TETKEY_ROTRIGHT) - { - check_pos.z = check_pos.z + 1; - piece_data = PieceShape(self.piece_type); - nudge = 1; - tetsnd("tetrot"); - } - else if (keyss & TETKEY_ROTLEFT) - { - check_pos.z = check_pos.z - 1; - piece_data = PieceShape(self.piece_type); - nudge = 1; - tetsnd("tetrot"); - } - // bounds check - if (check_pos.z > 3) - check_pos.z = 0; - else if (check_pos.z < 0) - check_pos.z = 3; - - // reality check - if (CheckMetrics(self.piece_type, check_pos.x, check_pos.y, check_pos.z)) - self.piece_pos = check_pos; - else if (brand_new) - { - self.tetris_on = 2; - self.tet_gameovertime = time + 5; - return; - } - else - { - for(i = 1; i <= nudge; ++i) - { - if(CheckMetrics(self.piece_type, check_pos.x + i, check_pos.y, check_pos.z)) - self.piece_pos = check_pos + '1 0 0' * i; - else if(CheckMetrics(self.piece_type, check_pos.x - i, check_pos.y, check_pos.z)) - self.piece_pos = check_pos - '1 0 0' * i; - else - continue; - break; - } - } - check_pos = self.piece_pos; - if(keyss & TETKEY_DROP) - { - // drop to bottom, but do NOT cement it yet - // this allows sliding it - ++check_pos.y; - while(CheckMetrics(self.piece_type, check_pos.x, check_pos.y + 1, check_pos.z)) - ++check_pos.y; - self.tet_autodown = time + 2 / (1 + Tetris_Level()); - } - else if (keyss & TETKEY_DOWN) - { - check_pos.y = check_pos.y + 1; - self.tet_autodown = time + 2 / (1 + Tetris_Level()); - } - else if (self.tet_autodown < time) - { - check_pos.y = check_pos.y + 1; - self.tet_autodown = time + 2 / (1 + Tetris_Level()); - } - if (CheckMetrics(self.piece_type, check_pos.x, check_pos.y, check_pos.z)) - { - if(old_pos != check_pos) - self.tet_drawtime = 0; - self.piece_pos = check_pos; - } - else - { - CementPiece(self.piece_type, self.piece_pos.x, self.piece_pos.y, self.piece_pos.z); - TetAddScore(1); - CompletedLines(); - self.piece_type = 0; - self.tet_drawtime = 0; - return; - } - CementPiece(self.piece_type, self.piece_pos.x, self.piece_pos.y, self.piece_pos.z); -} - -/* -********************************* - -Important Linking Into Quake stuff - -********************************* -*/ - - -void TetrisImpulse() -{ - if(self.tetris_on == 0 || self.tetris_on == 2) // from "off" or "game over" - { - self.tetris_on = 3; - - if(time < tet_vs_current_timeout) - { - // join VS game - self.tet_vs_id = tet_vs_current_id; - } - else - { - // start new VS game - ++tet_vs_current_id; - tet_vs_current_timeout = time + 15; - self.tet_vs_id = tet_vs_current_id; - bprint("^1TET^7R^1IS: ", self.netname, "^1 started a new game. Do ^7impulse 100^1 to join.\n"); - } - self.tet_highest_line = 0; - ResetTetris(); - self.tet_org = self.origin; - self.movetype = MOVETYPE_NOCLIP; - } - else if(self.tetris_on == 1) // from "on" - { - Tet_GameExit(); - self.impulse = 0; - } -} - - -float TetrisPreFrame() -{ - if (!self.tetris_on) - return 0; - - self.tet_org = self.origin; - if (self.tet_drawtime > time) - return 1; - Draw_Tetris(); - if(self.tetris_on == 3) - self.tet_drawtime = ceil(time - tet_vs_current_timeout + 0.1) + tet_vs_current_timeout; - else - self.tet_drawtime = time + 0.5; - return 1; -} -float frik_anglemoda(float v) -{ - return v - floor(v/360) * 360; -} -float angcompa(float y1, float y2) -{ - y1 = frik_anglemoda(y1); - y2 = frik_anglemoda(y2); - - float answer; - answer = y1 - y2; - if (answer > 180) - answer = answer - 360; - else if (answer < -180) - answer = answer + 360; - return answer; -} - -.float tetkey_down, tetkey_rotright, tetkey_left, tetkey_right, tetkey_rotleft, tetkey_drop; - -float TetrisKeyRepeat(.float fld, float f) -{ - if(f) - { - if(self.fld == 0) // initial key press - { - self.fld = time + 0.3; - return 1; - } - else if(time > self.fld) - { - self.fld = time + 0.1; - return 1; - } - else - { - // repeating too fast - return 0; - } - } - else - { - self.fld = 0; - return 0; - } -} - -float TetrisPostFrame() -{ - float keysa; - - keysa = 0; - - if (!self.tetris_on) - return 0; - - if(self.tetris_on == 2 && time > self.tet_gameovertime) - { - Tet_GameExit(); - return 0; - } - - if(self.tetris_on == 3 && time > tet_vs_current_timeout) - { - self.tetris_on = 1; // start VS game - self.tet_drawtime = 0; - } - - setorigin(self, self.tet_org); - self.movetype = MOVETYPE_NONE; - - if(self.tetris_on == 1) - { - if(TetrisKeyRepeat(tetkey_down, self.movement.x < 0)) - keysa |= TETKEY_DOWN; - - if(TetrisKeyRepeat(tetkey_rotright, self.movement.x > 0)) - keysa |= TETKEY_ROTRIGHT; - - if(TetrisKeyRepeat(tetkey_left, self.movement.y < 0)) - keysa |= TETKEY_LEFT; - - if(TetrisKeyRepeat(tetkey_right, self.movement.y > 0)) - keysa |= TETKEY_RIGHT; - - if(TetrisKeyRepeat(tetkey_rotleft, self.BUTTON_CROUCH)) - keysa |= TETKEY_ROTLEFT; - - if(TetrisKeyRepeat(tetkey_drop, self.BUTTON_JUMP)) - keysa |= TETKEY_DROP; - - HandleGame(keysa); - } - - return 1; -} - -#else - -float TetrisPostFrame() -{ - if (autocvar_g_bastet) - { - cvar_set("g_bastet", "0"); - LOG_INFO("The useless cvar has been toggled.\n"); - } - return 0; -} - -#endif diff --git a/qcsrc/server/mutators/mutator_breakablehook.qc b/qcsrc/server/mutators/mutator_breakablehook.qc new file mode 100644 index 000000000..149b3d761 --- /dev/null +++ b/qcsrc/server/mutators/mutator_breakablehook.qc @@ -0,0 +1,24 @@ +REGISTER_MUTATOR(bh, cvar("g_breakablehook")); + +bool autocvar_g_breakablehook; // allow toggling mid match? +bool autocvar_g_breakablehook_owner; + +MUTATOR_HOOKFUNCTION(bh, PlayerDamage_Calculate) +{ + if(frag_target.classname == "grapplinghook") + { + if((!autocvar_g_breakablehook) + || (!autocvar_g_breakablehook_owner && frag_attacker == frag_target.realowner) + ) { frag_damage = 0; } + + // hurt the owner of the hook + if(DIFF_TEAM(frag_attacker, frag_target.realowner)) + { + Damage (frag_target.realowner, frag_attacker, frag_attacker, 5, WEP_HOOK.m_id | HITTYPE_SPLASH, frag_target.realowner.origin, '0 0 0'); + RemoveGrapplingHook(frag_target.realowner); + return false; // dead + } + } + + return false; +} diff --git a/qcsrc/server/mutators/mutator_instagib.qc b/qcsrc/server/mutators/mutator_instagib.qc index a59bd0fe0..9728ed623 100644 --- a/qcsrc/server/mutators/mutator_instagib.qc +++ b/qcsrc/server/mutators/mutator_instagib.qc @@ -42,6 +42,14 @@ void instagib_ammocheck() instagib_stop_countdown(self); else if (self.ammo_cells > 0 || (self.items & IT_UNLIMITED_WEAPON_AMMO) || (self.flags & FL_GODMODE)) instagib_stop_countdown(self); + else if(autocvar_g_rm && autocvar_g_rm_laser) + { + if(!self.instagib_needammo) + { + Send_Notification(NOTIF_ONE, self, MSG_CENTER, CENTER_INSTAGIB_DOWNGRADE); + self.instagib_needammo = true; + } + } else { self.instagib_needammo = true; diff --git a/qcsrc/server/mutators/mutator_rocketminsta.qc b/qcsrc/server/mutators/mutator_rocketminsta.qc new file mode 100644 index 000000000..217d33f1a --- /dev/null +++ b/qcsrc/server/mutators/mutator_rocketminsta.qc @@ -0,0 +1,33 @@ +REGISTER_MUTATOR(rm, cvar("g_instagib")); + +MUTATOR_HOOKFUNCTION(rm, PlayerDamage_Calculate) +{ + // we do it this way, so rm can be toggled during the match + if(!autocvar_g_rm) { return false; } + + if(autocvar_g_rm) + if(DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR.m_id)) + if(frag_attacker == frag_target || frag_target.classname == "nade") + frag_damage = 0; + + if(autocvar_g_rm && autocvar_g_rm_laser == 1) //|| autocvar_g_rm_laser == 2) + if(DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO.m_id)) + if(frag_attacker == frag_target || (round_handler_IsActive() && !round_handler_IsRoundStarted())) + frag_damage = 0; + + return false; +} + +MUTATOR_HOOKFUNCTION(rm, PlayerDies) +{ + if( /*(DEATH_ISWEAPON(frag_deathtype, WEP_VAPORIZER))*/ + (autocvar_g_rm && DEATH_ISWEAPON(frag_deathtype, WEP_DEVASTATOR.m_id)) + || (((autocvar_g_rm && autocvar_g_rm_laser == 1) || autocvar_g_rm_laser == 2) && DEATH_ISWEAPON(frag_deathtype, WEP_ELECTRO.m_id)) + ) + frag_damage = 1000; // always gib if it was a vaporizer death + + //(get_weaponinfo(WEP_VAPORIZER)).weaponthrowable = 1; // throwing is forbidden by a mutator hook, enabling this for drop on death + + return false; +} + diff --git a/qcsrc/server/mutators/mutator_vampirehook.qc b/qcsrc/server/mutators/mutator_vampirehook.qc new file mode 100644 index 000000000..85ff7b058 --- /dev/null +++ b/qcsrc/server/mutators/mutator_vampirehook.qc @@ -0,0 +1,36 @@ +REGISTER_MUTATOR(vh, cvar("g_vampirehook")); + +bool autocvar_g_vampirehook_teamheal; +float autocvar_g_vampirehook_damage; +float autocvar_g_vampirehook_damagerate; +float autocvar_g_vampirehook_health_steal; + +.float last_dmg; + +MUTATOR_HOOKFUNCTION(vh, GrappleHookThink) +{ + entity dmgent = ((SAME_TEAM(self.owner, self.aiment) && autocvar_g_vampirehook_teamheal) ? self.owner : self.aiment); + + if(IS_PLAYER(self.aiment)) + if(self.last_dmg < time) + if(!self.aiment.frozen) + if(time >= game_starttime) + if(DIFF_TEAM(self.owner, self.aiment) || autocvar_g_vampirehook_teamheal) + if(self.aiment.health > 0) + if(autocvar_g_vampirehook_damage) + { + self.last_dmg = time + autocvar_g_vampirehook_damagerate; + self.owner.damage_dealt += autocvar_g_vampirehook_damage; + Damage(dmgent, self, self.owner, autocvar_g_vampirehook_damage, WEP_HOOK.m_id, self.origin, '0 0 0'); + if(SAME_TEAM(self.owner, self.aiment)) + self.aiment.health = min(self.aiment.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max); + else + self.owner.health = min(self.owner.health + autocvar_g_vampirehook_health_steal, g_pickup_healthsmall_max); + + if(dmgent == self.owner) + dmgent.health -= autocvar_g_vampirehook_damage; // FIXME: friendly fire?! + } + + return false; +} + diff --git a/qcsrc/server/mutators/mutators_include.qc b/qcsrc/server/mutators/mutators_include.qc index d403cb12f..7f238b88e 100644 --- a/qcsrc/server/mutators/mutators_include.qc +++ b/qcsrc/server/mutators/mutators_include.qc @@ -113,3 +113,6 @@ #include "mutator_nades.qc" #include "mutator_campcheck.qc" #include "mutator_buffs.qc" +#include "mutator_rocketminsta.qc" +#include "mutator_vampirehook.qc" +#include "mutator_breakablehook.qc" diff --git a/qcsrc/server/t_plats.qc b/qcsrc/server/t_plats.qc deleted file mode 100644 index ab25dbfbb..000000000 --- a/qcsrc/server/t_plats.qc +++ /dev/null @@ -1,2275 +0,0 @@ -#include "_all.qh" - -#include "bot/bot.qh" - -#include "command/common.qh" - -#include "g_damage.qh" -#include "item_key.qh" - -#include "../common/constants.qh" -#include "../common/deathtypes.qh" -#include "../common/notifications.qh" -#include "../common/util.qh" - -#include "../common/weapons/all.qh" - -#include "../csqcmodellib/sv_model.qh" - -#include "../warpzonelib/common.qh" -#include "../warpzonelib/util_server.qh" - -.float height; - -.float dmgtime2; -void generic_plat_blocked() -{ - if(self.dmg && other.takedamage != DAMAGE_NO) { - if(self.dmgtime2 < time) { - Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0'); - self.dmgtime2 = time + self.dmgtime; - } - - // Gib dead/dying stuff - if(other.deadflag != DEAD_NO) - Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0'); - } -} - - -.entity trigger_field; - -void() plat_center_touch; -void() plat_outside_touch; -void() plat_trigger_use; -void() plat_go_up; -void() plat_go_down; -void() plat_crush; -const float PLAT_LOW_TRIGGER = 1; - -void plat_spawn_inside_trigger() -{ - entity trigger; - vector tmin, tmax; - - trigger = spawn(); - trigger.touch = plat_center_touch; - trigger.movetype = MOVETYPE_NONE; - trigger.solid = SOLID_TRIGGER; - trigger.enemy = self; - - tmin = self.absmin + '25 25 0'; - tmax = self.absmax - '25 25 -8'; - tmin.z = tmax.z - (self.pos1_z - self.pos2_z + 8); - if (self.spawnflags & PLAT_LOW_TRIGGER) - tmax.z = tmin.z + 8; - - if (self.size.x <= 50) - { - tmin.x = (self.mins.x + self.maxs.x) / 2; - tmax.x = tmin.x + 1; - } - if (self.size.y <= 50) - { - tmin.y = (self.mins.y + self.maxs.y) / 2; - tmax.y = tmin.y + 1; - } - - if(tmin.x < tmax.x) - if(tmin.y < tmax.y) - if(tmin.z < tmax.z) - { - setsize (trigger, tmin, tmax); - return; - } - - // otherwise, something is fishy... - remove(trigger); - objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn"); -} - -void plat_hit_top() -{ - sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM); - self.state = 1; - self.think = plat_go_down; - self.nextthink = self.ltime + 3; -} - -void plat_hit_bottom() -{ - sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM); - self.state = 2; -} - -void plat_go_down() -{ - sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM); - self.state = 3; - SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom); -} - -void plat_go_up() -{ - sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM); - self.state = 4; - SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top); -} - -void plat_center_touch() -{ - if (!other.iscreature) - return; - - if (other.health <= 0) - return; - - self = self.enemy; - if (self.state == 2) - plat_go_up (); - else if (self.state == 1) - self.nextthink = self.ltime + 1; // delay going down -} - -void plat_outside_touch() -{ - if (!other.iscreature) - return; - - if (other.health <= 0) - return; - - self = self.enemy; - if (self.state == 1) - plat_go_down (); -} - -void plat_trigger_use() -{ - if (self.think) - return; // already activated - plat_go_down(); -} - - -void plat_crush() -{ - if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!! - Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0'); - } else { - if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite? - Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0'); - // Gib dead/dying stuff - if(other.deadflag != DEAD_NO) - Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0'); - } - - if (self.state == 4) - plat_go_down (); - else if (self.state == 3) - plat_go_up (); - // when in other states, then the plat_crush event came delayed after - // plat state already had changed - // this isn't a bug per se! - } -} - -void plat_use() -{ - self.use = func_null; - if (self.state != 4) - objerror ("plat_use: not in up state"); - plat_go_down(); -} - -.string sound1, sound2; - -void plat_reset() -{ - IFTARGETED - { - setorigin (self, self.pos1); - self.state = 4; - self.use = plat_use; - } - else - { - setorigin (self, self.pos2); - self.state = 2; - self.use = plat_trigger_use; - } -} - -.float platmovetype_start_default, platmovetype_end_default; -float set_platmovetype(entity e, string s) -{ - // sets platmovetype_start and platmovetype_end based on a string consisting of two values - - float n; - n = tokenize_console(s); - if(n > 0) - e.platmovetype_start = stof(argv(0)); - else - e.platmovetype_start = 0; - - if(n > 1) - e.platmovetype_end = stof(argv(1)); - else - e.platmovetype_end = e.platmovetype_start; - - if(n > 2) - if(argv(2) == "force") - return true; // no checking, return immediately - - if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end)) - { - objerror("Invalid platform move type; platform would go in reverse, which is not allowed."); - return false; - } - - return true; -} - -void spawnfunc_path_corner() -{ - // setup values for overriding train movement - // if a second value does not exist, both start and end speeds are the single value specified - if(!set_platmovetype(self, self.platmovetype)) - return; -} -void spawnfunc_func_plat() -{ - if (self.sounds == 0) - self.sounds = 2; - - if(self.spawnflags & 4) - self.dmg = 10000; - - if(self.dmg && (self.message == "")) - self.message = "was squished"; - if(self.dmg && (self.message2 == "")) - self.message2 = "was squished by"; - - if (self.sounds == 1) - { - precache_sound ("plats/plat1.wav"); - precache_sound ("plats/plat2.wav"); - self.noise = "plats/plat1.wav"; - self.noise1 = "plats/plat2.wav"; - } - - if (self.sounds == 2) - { - precache_sound ("plats/medplat1.wav"); - precache_sound ("plats/medplat2.wav"); - self.noise = "plats/medplat1.wav"; - self.noise1 = "plats/medplat2.wav"; - } - - if (self.sound1) - { - precache_sound (self.sound1); - self.noise = self.sound1; - } - if (self.sound2) - { - precache_sound (self.sound2); - self.noise1 = self.sound2; - } - - self.mangle = self.angles; - self.angles = '0 0 0'; - - self.classname = "plat"; - if (!InitMovingBrushTrigger()) - return; - self.effects |= EF_LOWPRECISION; - setsize (self, self.mins , self.maxs); - - self.blocked = plat_crush; - - if (!self.speed) - self.speed = 150; - if (!self.lip) - self.lip = 16; - if (!self.height) - self.height = self.size.z - self.lip; - - self.pos1 = self.origin; - self.pos2 = self.origin; - self.pos2_z = self.origin.z - self.height; - - self.reset = plat_reset; - plat_reset(); - - plat_spawn_inside_trigger (); // the "start moving" trigger -} - -.float train_wait_turning; -void() train_next; -void train_wait() -{ - entity oldself; - oldself = self; - self = self.enemy; - SUB_UseTargets(); - self = oldself; - self.enemy = world; - - // if turning is enabled, the train will turn toward the next point while waiting - if(self.platmovetype_turn && !self.train_wait_turning) - { - entity targ, cp; - vector ang; - targ = find(world, targetname, self.target); - if((self.spawnflags & 1) && targ.curvetarget) - cp = find(world, targetname, targ.curvetarget); - else - cp = world; - - if(cp) // bezier curves movement - ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner - else // linear movement - ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner - ang = vectoangles(ang); - ang.x = -ang.x; // flip up / down orientation - - if(self.wait > 0) // slow turning - SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait); - else // instant turning - SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait); - self.train_wait_turning = true; - return; - } - - if(self.noise != "") - stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway - - if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning - { - self.train_wait_turning = false; - train_next(); - } - else - { - self.think = train_next; - self.nextthink = self.ltime + self.wait; - } -} - -void train_next() -{ - entity targ, cp = world; - vector cp_org = '0 0 0'; - - targ = find(world, targetname, self.target); - self.target = targ.target; - if (self.spawnflags & 1) - { - if(targ.curvetarget) - { - cp = find(world, targetname, targ.curvetarget); // get its second target (the control point) - cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination - } - } - if (self.target == "") - objerror("train_next: no next target"); - self.wait = targ.wait; - if (!self.wait) - self.wait = 0.1; - - if(targ.platmovetype) - { - // this path_corner contains a movetype overrider, apply it - self.platmovetype_start = targ.platmovetype_start; - self.platmovetype_end = targ.platmovetype_end; - } - else - { - // this path_corner doesn't contain a movetype overrider, use the train's defaults - self.platmovetype_start = self.platmovetype_start_default; - self.platmovetype_end = self.platmovetype_end_default; - } - - if (targ.speed) - { - if (cp) - SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait); - else - SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait); - } - else - { - if (cp) - SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait); - else - SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait); - } - - if(self.noise != "") - sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE); -} - -void func_train_find() -{ - entity targ; - targ = find(world, targetname, self.target); - self.target = targ.target; - if (self.target == "") - objerror("func_train_find: no next target"); - setorigin(self, targ.origin - self.view_ofs); - self.nextthink = self.ltime + 1; - self.think = train_next; -} - -/*QUAKED spawnfunc_func_train (0 .5 .8) ? -Ridable platform, targets spawnfunc_path_corner path to follow. -speed : speed the train moves (can be overridden by each spawnfunc_path_corner) -target : targetname of first spawnfunc_path_corner (starts here) -*/ -void spawnfunc_func_train() -{ - if (self.noise != "") - precache_sound(self.noise); - - if (self.target == "") - objerror("func_train without a target"); - if (!self.speed) - self.speed = 100; - - if (!InitMovingBrushTrigger()) - return; - self.effects |= EF_LOWPRECISION; - - if (self.spawnflags & 2) - { - self.platmovetype_turn = true; - self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now - } - else - self.view_ofs = self.mins; - - // wait for targets to spawn - InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION); - - self.blocked = generic_plat_blocked; - if(self.dmg && (self.message == "")) - self.message = " was squished"; - if(self.dmg && (self.message2 == "")) - self.message2 = "was squished by"; - if(self.dmg && (!self.dmgtime)) - self.dmgtime = 0.25; - self.dmgtime2 = time; - - if(!set_platmovetype(self, self.platmovetype)) - return; - self.platmovetype_start_default = self.platmovetype_start; - self.platmovetype_end_default = self.platmovetype_end; - - // TODO make a reset function for this one -} - -void func_rotating_setactive(float astate) -{ - - if (astate == ACTIVE_TOGGLE) - { - if(self.active == ACTIVE_ACTIVE) - self.active = ACTIVE_NOT; - else - self.active = ACTIVE_ACTIVE; - } - else - self.active = astate; - - if(self.active == ACTIVE_NOT) - self.avelocity = '0 0 0'; - else - self.avelocity = self.pos1; -} - -/*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS -Brush model that spins in place on one axis (default Z). -speed : speed to rotate (in degrees per second) -noise : path/name of looping .wav file to play. -dmg : Do this mutch dmg every .dmgtime intervall when blocked -dmgtime : See above. -*/ - -void spawnfunc_func_rotating() -{ - if (self.noise != "") - { - precache_sound(self.noise); - ambientsound(self.origin, self.noise, VOL_BASE, ATTEN_IDLE); - } - - self.active = ACTIVE_ACTIVE; - self.setactive = func_rotating_setactive; - - if (!self.speed) - self.speed = 100; - // FIXME: test if this turns the right way, then remove this comment (negate as needed) - if (self.spawnflags & 4) // X (untested) - self.avelocity = '0 0 1' * self.speed; - // FIXME: test if this turns the right way, then remove this comment (negate as needed) - else if (self.spawnflags & 8) // Y (untested) - self.avelocity = '1 0 0' * self.speed; - // FIXME: test if this turns the right way, then remove this comment (negate as needed) - else // Z - self.avelocity = '0 1 0' * self.speed; - - self.pos1 = self.avelocity; - - if(self.dmg && (self.message == "")) - self.message = " was squished"; - if(self.dmg && (self.message2 == "")) - self.message2 = "was squished by"; - - - if(self.dmg && (!self.dmgtime)) - self.dmgtime = 0.25; - - self.dmgtime2 = time; - - if (!InitMovingBrushTrigger()) - return; - // no EF_LOWPRECISION here, as rounding angles is bad - - self.blocked = generic_plat_blocked; - - // wait for targets to spawn - self.nextthink = self.ltime + 999999999; - self.think = SUB_NullThink; // for PushMove - - // TODO make a reset function for this one -} - -.float height; -void func_bobbing_controller_think() -{ - vector v; - self.nextthink = time + 0.1; - - if(self.owner.active != ACTIVE_ACTIVE) - { - self.owner.velocity = '0 0 0'; - return; - } - - // calculate sinewave using makevectors - makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0'); - v = self.owner.destvec + self.owner.movedir * v_forward.y; - if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed - // * 10 so it will arrive in 0.1 sec - self.owner.velocity = (v - self.owner.origin) * 10; -} - -/*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS -Brush model that moves back and forth on one axis (default Z). -speed : how long one cycle takes in seconds (default 4) -height : how far the cycle moves (default 32) -phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0) -noise : path/name of looping .wav file to play. -dmg : Do this mutch dmg every .dmgtime intervall when blocked -dmgtime : See above. -*/ -void spawnfunc_func_bobbing() -{ - entity controller; - if (self.noise != "") - { - precache_sound(self.noise); - soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE); - } - if (!self.speed) - self.speed = 4; - if (!self.height) - self.height = 32; - // center of bobbing motion - self.destvec = self.origin; - // time scale to get degrees - self.cnt = 360 / self.speed; - - self.active = ACTIVE_ACTIVE; - - // damage when blocked - self.blocked = generic_plat_blocked; - if(self.dmg && (self.message == "")) - self.message = " was squished"; - if(self.dmg && (self.message2 == "")) - self.message2 = "was squished by"; - if(self.dmg && (!self.dmgtime)) - self.dmgtime = 0.25; - self.dmgtime2 = time; - - // how far to bob - if (self.spawnflags & 1) // X - self.movedir = '1 0 0' * self.height; - else if (self.spawnflags & 2) // Y - self.movedir = '0 1 0' * self.height; - else // Z - self.movedir = '0 0 1' * self.height; - - if (!InitMovingBrushTrigger()) - return; - - // wait for targets to spawn - controller = spawn(); - controller.classname = "func_bobbing_controller"; - controller.owner = self; - controller.nextthink = time + 1; - controller.think = func_bobbing_controller_think; - self.nextthink = self.ltime + 999999999; - self.think = SUB_NullThink; // for PushMove - - // Savage: Reduce bandwith, critical on e.g. nexdm02 - self.effects |= EF_LOWPRECISION; - - // TODO make a reset function for this one -} - -.float freq; -void func_pendulum_controller_think() -{ - float v; - self.nextthink = time + 0.1; - - if (!(self.owner.active == ACTIVE_ACTIVE)) - { - self.owner.avelocity_x = 0; - return; - } - - // calculate sinewave using makevectors - makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0'); - v = self.owner.speed * v_forward.y + self.cnt; - if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed - { - // * 10 so it will arrive in 0.1 sec - self.owner.avelocity_z = (remainder(v - self.owner.angles.z, 360)) * 10; - } -} - -void spawnfunc_func_pendulum() -{ - entity controller; - if (self.noise != "") - { - precache_sound(self.noise); - soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE); - } - - self.active = ACTIVE_ACTIVE; - - // keys: angle, speed, phase, noise, freq - - if(!self.speed) - self.speed = 30; - // not initializing self.dmg to 2, to allow damageless pendulum - - if(self.dmg && (self.message == "")) - self.message = " was squished"; - if(self.dmg && (self.message2 == "")) - self.message2 = "was squished by"; - if(self.dmg && (!self.dmgtime)) - self.dmgtime = 0.25; - self.dmgtime2 = time; - - self.blocked = generic_plat_blocked; - - self.avelocity_z = 0.0000001; - if (!InitMovingBrushTrigger()) - return; - - if(!self.freq) - { - // find pendulum length (same formula as Q3A) - self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins.z)))); - } - - // copy initial angle - self.cnt = self.angles.z; - - // wait for targets to spawn - controller = spawn(); - controller.classname = "func_pendulum_controller"; - controller.owner = self; - controller.nextthink = time + 1; - controller.think = func_pendulum_controller_think; - self.nextthink = self.ltime + 999999999; - self.think = SUB_NullThink; // for PushMove - - //self.effects |= EF_LOWPRECISION; - - // TODO make a reset function for this one -} - -// button and multiple button - -void() button_wait; -void() button_return; - -void button_wait() -{ - self.state = STATE_TOP; - self.nextthink = self.ltime + self.wait; - self.think = button_return; - activator = self.enemy; - SUB_UseTargets(); - self.frame = 1; // use alternate textures -} - -void button_done() -{ - self.state = STATE_BOTTOM; -} - -void button_return() -{ - self.state = STATE_DOWN; - SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done); - self.frame = 0; // use normal textures - if (self.health) - self.takedamage = DAMAGE_YES; // can be shot again -} - - -void button_blocked() -{ - // do nothing, just don't come all the way back out -} - - -void button_fire() -{ - self.health = self.max_health; - self.takedamage = DAMAGE_NO; // will be reset upon return - - if (self.state == STATE_UP || self.state == STATE_TOP) - return; - - if (self.noise != "") - sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM); - - self.state = STATE_UP; - SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait); -} - -void button_reset() -{ - self.health = self.max_health; - setorigin(self, self.pos1); - self.frame = 0; // use normal textures - self.state = STATE_BOTTOM; - if (self.health) - self.takedamage = DAMAGE_YES; // can be shot again -} - -void button_use() -{ - if(self.active != ACTIVE_ACTIVE) - return; - - self.enemy = activator; - button_fire (); -} - -void button_touch() -{ - if (!other) - return; - if (!other.iscreature) - return; - if(other.velocity * self.movedir < 0) - return; - self.enemy = other; - if (other.owner) - self.enemy = other.owner; - button_fire (); -} - -void button_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{ - if(self.spawnflags & DOOR_NOSPLASH) - if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH)) - return; - self.health = self.health - damage; - if (self.health <= 0) - { - self.enemy = damage_attacker; - button_fire (); - } -} - - -/*QUAKED spawnfunc_func_button (0 .5 .8) ? -When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again. - -"angle" determines the opening direction -"target" all entities with a matching targetname will be used -"speed" override the default 40 speed -"wait" override the default 1 second wait (-1 = never return) -"lip" override the default 4 pixel lip remaining at end of move -"health" if set, the button must be killed instead of touched. If set to -1, the button will fire on ANY attack, even damageless ones like the InstaGib laser -"sounds" -0) steam metal -1) wooden clunk -2) metallic click -3) in-out -*/ -void spawnfunc_func_button() -{ - SetMovedir (); - - if (!InitMovingBrushTrigger()) - return; - self.effects |= EF_LOWPRECISION; - - self.blocked = button_blocked; - self.use = button_use; - -// if (self.health == 0) // all buttons are now shootable -// self.health = 10; - if (self.health) - { - self.max_health = self.health; - self.event_damage = button_damage; - self.takedamage = DAMAGE_YES; - } - else - self.touch = button_touch; - - if (!self.speed) - self.speed = 40; - if (!self.wait) - self.wait = 1; - if (!self.lip) - self.lip = 4; - - if(self.noise != "") - precache_sound(self.noise); - - self.active = ACTIVE_ACTIVE; - - self.pos1 = self.origin; - self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip); - self.flags |= FL_NOTARGET; - - button_reset(); -} - - -const float DOOR_START_OPEN = 1; -const float DOOR_DONT_LINK = 4; -const float DOOR_TOGGLE = 32; - -/* - -Doors are similar to buttons, but can spawn a fat trigger field around them -to open without a touch, and they link together to form simultanious -double/quad doors. - -Door.owner is the master door. If there is only one door, it points to itself. -If multiple doors, all will point to a single one. - -Door.enemy chains from the master door through all doors linked in the chain. - -*/ - -/* -============================================================================= - -THINK FUNCTIONS - -============================================================================= -*/ - -void() door_go_down; -void() door_go_up; -void() door_rotating_go_down; -void() door_rotating_go_up; - -void door_blocked() -{ - - if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!! - Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0'); - } else { - - if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite? - Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0'); - - //Dont chamge direction for dead or dying stuff - if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) { - if (self.wait >= 0) - { - if (self.state == STATE_DOWN) - if (self.classname == "door") - { - door_go_up (); - } else - { - door_rotating_go_up (); - } - else - if (self.classname == "door") - { - door_go_down (); - } else - { - door_rotating_go_down (); - } - } - } else { - //gib dying stuff just to make sure - if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite? - Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0'); - } - } - - //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic); -// if a door has a negative wait, it would never come back if blocked, -// so let it just squash the object to death real fast -/* if (self.wait >= 0) - { - if (self.state == STATE_DOWN) - door_go_up (); - else - door_go_down (); - } -*/ -} - - -void door_hit_top() -{ - if (self.noise1 != "") - sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM); - self.state = STATE_TOP; - if (self.spawnflags & DOOR_TOGGLE) - return; // don't come down automatically - if (self.classname == "door") - { - self.think = door_go_down; - } else - { - self.think = door_rotating_go_down; - } - self.nextthink = self.ltime + self.wait; -} - -void door_hit_bottom() -{ - if (self.noise1 != "") - sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM); - self.state = STATE_BOTTOM; -} - -void door_go_down() -{ - if (self.noise2 != "") - sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM); - if (self.max_health) - { - self.takedamage = DAMAGE_YES; - self.health = self.max_health; - } - - self.state = STATE_DOWN; - SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom); -} - -void door_go_up() -{ - if (self.state == STATE_UP) - return; // already going up - - if (self.state == STATE_TOP) - { // reset top wait time - self.nextthink = self.ltime + self.wait; - return; - } - - if (self.noise2 != "") - sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM); - self.state = STATE_UP; - SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top); - - string oldmessage; - oldmessage = self.message; - self.message = ""; - SUB_UseTargets(); - self.message = oldmessage; -} - - - -/* -============================================================================= - -ACTIVATION FUNCTIONS - -============================================================================= -*/ - -float door_check_keys(void) { - entity door = self.owner ? self.owner : self; - - // no key needed - if (!door.itemkeys) - return true; - - // this door require a key - // only a player can have a key - if (!IS_PLAYER(other)) - return false; - - if (item_keys_usekey(door, other)) { - // some keys were used - if (other.key_door_messagetime <= time) { - play2(other, "misc/talk.wav"); - Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys)); - other.key_door_messagetime = time + 2; - } - } else { - // no keys were used - if (other.key_door_messagetime <= time) { - play2(other, "misc/talk.wav"); - Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys)); - other.key_door_messagetime = time + 2; - } - } - - if (door.itemkeys) { - // door is now unlocked - play2(other, "misc/talk.wav"); - Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED); - return true; - } else - return false; -} - - -void door_fire() -{ - entity oself; - entity starte; - - if (self.owner != self) - objerror ("door_fire: self.owner != self"); - - oself = self; - - if (self.spawnflags & DOOR_TOGGLE) - { - if (self.state == STATE_UP || self.state == STATE_TOP) - { - starte = self; - do - { - if (self.classname == "door") - { - door_go_down (); - } - else - { - door_rotating_go_down (); - } - self = self.enemy; - } while ( (self != starte) && (self != world) ); - self = oself; - return; - } - } - -// trigger all paired doors - starte = self; - do - { - if (self.classname == "door") - { - door_go_up (); - } else - { - // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction - if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM) - { - self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating - self.pos2 = '0 0 0' - self.pos2; - } - // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side - if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN - && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0))))) - { - door_rotating_go_up (); - } - } - self = self.enemy; - } while ( (self != starte) && (self != world) ); - self = oself; -} - - -void door_use() -{ - entity oself; - - //dprint("door_use (model: ");dprint(self.model);dprint(")\n"); - - if (self.owner) - { - oself = self; - self = self.owner; - door_fire (); - self = oself; - } -} - - -void door_trigger_touch() -{ - if (other.health < 1) - if (!(other.iscreature && other.deadflag == DEAD_NO)) - return; - - if (time < self.attack_finished_single) - return; - - // check if door is locked - if (!door_check_keys()) - return; - - self.attack_finished_single = time + 1; - - activator = other; - - self = self.owner; - door_use (); -} - - -void door_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{ - entity oself; - if(self.spawnflags & DOOR_NOSPLASH) - if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH)) - return; - self.health = self.health - damage; - - if (self.itemkeys) { - // don't allow opening doors through damage if keys are required - return; - } - - if (self.health <= 0) - { - oself = self; - self = self.owner; - self.health = self.max_health; - self.takedamage = DAMAGE_NO; // wil be reset upon return - door_use (); - self = oself; - } -} - - -/* -================ -door_touch - -Prints messages -================ -*/ -void door_touch() -{ - if (!IS_PLAYER(other)) - return; - if (self.owner.attack_finished_single > time) - return; - - self.owner.attack_finished_single = time + 2; - - if (!(self.owner.dmg) && (self.owner.message != "")) - { - if (IS_CLIENT(other)) - centerprint(other, self.owner.message); - play2(other, "misc/talk.wav"); - } -} - - -void door_generic_plat_blocked() -{ - - if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!! - Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0'); - } else { - - if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite? - Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0'); - - //Dont chamge direction for dead or dying stuff - if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) { - if (self.wait >= 0) - { - if (self.state == STATE_DOWN) - door_rotating_go_up (); - else - door_rotating_go_down (); - } - } else { - //gib dying stuff just to make sure - if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite? - Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0'); - } - } - - //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic); -// if a door has a negative wait, it would never come back if blocked, -// so let it just squash the object to death real fast -/* if (self.wait >= 0) - { - if (self.state == STATE_DOWN) - door_rotating_go_up (); - else - door_rotating_go_down (); - } -*/ -} - - -void door_rotating_hit_top() -{ - if (self.noise1 != "") - sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM); - self.state = STATE_TOP; - if (self.spawnflags & DOOR_TOGGLE) - return; // don't come down automatically - self.think = door_rotating_go_down; - self.nextthink = self.ltime + self.wait; -} - -void door_rotating_hit_bottom() -{ - if (self.noise1 != "") - sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM); - if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating - { - self.pos2 = '0 0 0' - self.pos2; - self.lip = 0; - } - self.state = STATE_BOTTOM; -} - -void door_rotating_go_down() -{ - if (self.noise2 != "") - sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM); - if (self.max_health) - { - self.takedamage = DAMAGE_YES; - self.health = self.max_health; - } - - self.state = STATE_DOWN; - SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom); -} - -void door_rotating_go_up() -{ - if (self.state == STATE_UP) - return; // already going up - - if (self.state == STATE_TOP) - { // reset top wait time - self.nextthink = self.ltime + self.wait; - return; - } - if (self.noise2 != "") - sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM); - self.state = STATE_UP; - SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top); - - string oldmessage; - oldmessage = self.message; - self.message = ""; - SUB_UseTargets(); - self.message = oldmessage; -} - - - - -/* -============================================================================= - -SPAWNING FUNCTIONS - -============================================================================= -*/ - - -entity spawn_field(vector fmins, vector fmaxs) -{ - entity trigger; - vector t1, t2; - - trigger = spawn(); - trigger.classname = "doortriggerfield"; - trigger.movetype = MOVETYPE_NONE; - trigger.solid = SOLID_TRIGGER; - trigger.owner = self; - trigger.touch = door_trigger_touch; - - t1 = fmins; - t2 = fmaxs; - setsize (trigger, t1 - '60 60 8', t2 + '60 60 8'); - return (trigger); -} - - -entity LinkDoors_nextent(entity cur, entity near, entity pass) -{ - while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy)) - { - } - return cur; -} - -float LinkDoors_isconnected(entity e1, entity e2, entity pass) -{ - float DELTA = 4; - if (e1.absmin.x > e2.absmax.x + DELTA) - return false; - if (e1.absmin.y > e2.absmax.y + DELTA) - return false; - if (e1.absmin.z > e2.absmax.z + DELTA) - return false; - if (e2.absmin.x > e1.absmax.x + DELTA) - return false; - if (e2.absmin.y > e1.absmax.y + DELTA) - return false; - if (e2.absmin.z > e1.absmax.z + DELTA) - return false; - return true; -} - -/* -============= -LinkDoors - - -============= -*/ -void LinkDoors() -{ - entity t; - vector cmins, cmaxs; - - if (self.enemy) - return; // already linked by another door - if (self.spawnflags & 4) - { - self.owner = self.enemy = self; - - if (self.health) - return; - IFTARGETED - return; - if (self.items) - return; - self.trigger_field = spawn_field(self.absmin, self.absmax); - - return; // don't want to link this door - } - - FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world); - - // set owner, and make a loop of the chain - LOG_TRACE("LinkDoors: linking doors:"); - for(t = self; ; t = t.enemy) - { - LOG_TRACE(" ", etos(t)); - t.owner = self; - if(t.enemy == world) - { - t.enemy = self; - break; - } - } - LOG_TRACE("\n"); - - // collect health, targetname, message, size - cmins = self.absmin; - cmaxs = self.absmax; - for(t = self; ; t = t.enemy) - { - if(t.health && !self.health) - self.health = t.health; - if((t.targetname != "") && (self.targetname == "")) - self.targetname = t.targetname; - if((t.message != "") && (self.message == "")) - self.message = t.message; - if (t.absmin.x < cmins.x) - cmins.x = t.absmin.x; - if (t.absmin.y < cmins.y) - cmins.y = t.absmin.y; - if (t.absmin.z < cmins.z) - cmins.z = t.absmin.z; - if (t.absmax.x > cmaxs.x) - cmaxs.x = t.absmax.x; - if (t.absmax.y > cmaxs.y) - cmaxs.y = t.absmax.y; - if (t.absmax.z > cmaxs.z) - cmaxs.z = t.absmax.z; - if(t.enemy == self) - break; - } - - // distribute health, targetname, message - for(t = self; t; t = t.enemy) - { - t.health = self.health; - t.targetname = self.targetname; - t.message = self.message; - if(t.enemy == self) - break; - } - - // shootable, or triggered doors just needed the owner/enemy links, - // they don't spawn a field - - if (self.health) - return; - IFTARGETED - return; - if (self.items) - return; - - self.trigger_field = spawn_field(cmins, cmaxs); -} - - -/*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE -if two doors touch, they are assumed to be connected and operate as a unit. - -TOGGLE causes the door to wait in both the start and end states for a trigger event. - -START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors). - -GOLD_KEY causes the door to open only if the activator holds a gold key. - -SILVER_KEY causes the door to open only if the activator holds a silver key. - -"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet -"angle" determines the opening direction -"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. -"health" if set, door must be shot open -"speed" movement speed (100 default) -"wait" wait before returning (3 default, -1 = never return) -"lip" lip remaining at end of move (8 default) -"dmg" damage to inflict when blocked (2 default) -"sounds" -0) no sound -1) stone -2) base -3) stone chain -4) screechy metal -FIXME: only one sound set available at the time being - -*/ - -void door_init_startopen() -{ - setorigin (self, self.pos2); - self.pos2 = self.pos1; - self.pos1 = self.origin; -} - -void door_reset() -{ - setorigin(self, self.pos1); - self.velocity = '0 0 0'; - self.state = STATE_BOTTOM; - self.think = func_null; - self.nextthink = 0; -} - -// spawnflags require key (for now only func_door) -const float SPAWNFLAGS_GOLD_KEY = 8; -const float SPAWNFLAGS_SILVER_KEY = 16; -void spawnfunc_func_door() -{ - // Quake 1 keys compatibility - if (self.spawnflags & SPAWNFLAGS_GOLD_KEY) - self.itemkeys |= ITEM_KEY_BIT(0); - if (self.spawnflags & SPAWNFLAGS_SILVER_KEY) - self.itemkeys |= ITEM_KEY_BIT(1); - - //if (!self.deathtype) // map makers can override this - // self.deathtype = " got in the way"; - SetMovedir (); - - self.max_health = self.health; - if (!InitMovingBrushTrigger()) - return; - self.effects |= EF_LOWPRECISION; - self.classname = "door"; - - self.blocked = door_blocked; - self.use = door_use; - - // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY - // if(self.spawnflags & 8) - // self.dmg = 10000; - - if(self.dmg && (self.message == "")) - self.message = "was squished"; - if(self.dmg && (self.message2 == "")) - self.message2 = "was squished by"; - - if (self.sounds > 0) - { - precache_sound ("plats/medplat1.wav"); - precache_sound ("plats/medplat2.wav"); - self.noise2 = "plats/medplat1.wav"; - self.noise1 = "plats/medplat2.wav"; - } - - if (!self.speed) - self.speed = 100; - if (!self.wait) - self.wait = 3; - if (!self.lip) - self.lip = 8; - - self.pos1 = self.origin; - self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip); - -// DOOR_START_OPEN is to allow an entity to be lighted in the closed position -// but spawn in the open position - if (self.spawnflags & DOOR_START_OPEN) - InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION); - - self.state = STATE_BOTTOM; - - if (self.health) - { - self.takedamage = DAMAGE_YES; - self.event_damage = door_damage; - } - - if (self.items) - self.wait = -1; - - self.touch = door_touch; - -// LinkDoors can't be done until all of the doors have been spawned, so -// the sizes can be detected properly. - InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS); - - self.reset = door_reset; -} - -/*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS -if two doors touch, they are assumed to be connected and operate as a unit. - -TOGGLE causes the door to wait in both the start and end states for a trigger event. - -BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor. -The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction -must have set trigger_reverse to 1. -BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side. - -START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors). - -"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet -"angle" determines the destination angle for opening. negative values reverse the direction. -"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. -"health" if set, door must be shot open -"speed" movement speed (100 default) -"wait" wait before returning (3 default, -1 = never return) -"dmg" damage to inflict when blocked (2 default) -"sounds" -0) no sound -1) stone -2) base -3) stone chain -4) screechy metal -FIXME: only one sound set available at the time being -*/ - -void door_rotating_reset() -{ - self.angles = self.pos1; - self.avelocity = '0 0 0'; - self.state = STATE_BOTTOM; - self.think = func_null; - self.nextthink = 0; -} - -void door_rotating_init_startopen() -{ - self.angles = self.movedir; - self.pos2 = '0 0 0'; - self.pos1 = self.movedir; -} - - -void spawnfunc_func_door_rotating() -{ - - //if (!self.deathtype) // map makers can override this - // self.deathtype = " got in the way"; - - // I abuse "movedir" for denoting the axis for now - if (self.spawnflags & 64) // X (untested) - self.movedir = '0 0 1'; - else if (self.spawnflags & 128) // Y (untested) - self.movedir = '1 0 0'; - else // Z - self.movedir = '0 1 0'; - - if (self.angles.y ==0) self.angles_y = 90; - - self.movedir = self.movedir * self.angles.y; - self.angles = '0 0 0'; - - self.max_health = self.health; - self.avelocity = self.movedir; - if (!InitMovingBrushTrigger()) - return; - self.velocity = '0 0 0'; - //self.effects |= EF_LOWPRECISION; - self.classname = "door_rotating"; - - self.blocked = door_blocked; - self.use = door_use; - - if(self.spawnflags & 8) - self.dmg = 10000; - - if(self.dmg && (self.message == "")) - self.message = "was squished"; - if(self.dmg && (self.message2 == "")) - self.message2 = "was squished by"; - - if (self.sounds > 0) - { - precache_sound ("plats/medplat1.wav"); - precache_sound ("plats/medplat2.wav"); - self.noise2 = "plats/medplat1.wav"; - self.noise1 = "plats/medplat2.wav"; - } - - if (!self.speed) - self.speed = 50; - if (!self.wait) - self.wait = 1; - self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating - - self.pos1 = '0 0 0'; - self.pos2 = self.movedir; - -// DOOR_START_OPEN is to allow an entity to be lighted in the closed position -// but spawn in the open position - if (self.spawnflags & DOOR_START_OPEN) - InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION); - - self.state = STATE_BOTTOM; - - if (self.health) - { - self.takedamage = DAMAGE_YES; - self.event_damage = door_damage; - } - - if (self.items) - self.wait = -1; - - self.touch = door_touch; - -// LinkDoors can't be done until all of the doors have been spawned, so -// the sizes can be detected properly. - InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS); - - self.reset = door_rotating_reset; -} - -/* -============================================================================= - -SECRET DOORS - -============================================================================= -*/ - -void() fd_secret_move1; -void() fd_secret_move2; -void() fd_secret_move3; -void() fd_secret_move4; -void() fd_secret_move5; -void() fd_secret_move6; -void() fd_secret_done; - -const float SECRET_OPEN_ONCE = 1; // stays open -const float SECRET_1ST_LEFT = 2; // 1st move is left of arrow -const float SECRET_1ST_DOWN = 4; // 1st move is down from arrow -const float SECRET_NO_SHOOT = 8; // only opened by trigger -const float SECRET_YES_SHOOT = 16; // shootable even if targeted - -void fd_secret_use() -{ - float temp; - string message_save; - - self.health = 10000; - self.bot_attack = true; - - // exit if still moving around... - if (self.origin != self.oldorigin) - return; - - message_save = self.message; - self.message = ""; // no more message - SUB_UseTargets(); // fire all targets / killtargets - self.message = message_save; - - self.velocity = '0 0 0'; - - // Make a sound, wait a little... - - if (self.noise1 != "") - sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM); - self.nextthink = self.ltime + 0.1; - - temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1 - makevectors(self.mangle); - - if (!self.t_width) - { - if (self.spawnflags & SECRET_1ST_DOWN) - self.t_width = fabs(v_up * self.size); - else - self.t_width = fabs(v_right * self.size); - } - - if (!self.t_length) - self.t_length = fabs(v_forward * self.size); - - if (self.spawnflags & SECRET_1ST_DOWN) - self.dest1 = self.origin - v_up * self.t_width; - else - self.dest1 = self.origin + v_right * (self.t_width * temp); - - self.dest2 = self.dest1 + v_forward * self.t_length; - SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1); - if (self.noise2 != "") - sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM); -} - -void fd_secret_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force) -{ - fd_secret_use(); -} - -// Wait after first movement... -void fd_secret_move1() -{ - self.nextthink = self.ltime + 1.0; - self.think = fd_secret_move2; - if (self.noise3 != "") - sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM); -} - -// Start moving sideways w/sound... -void fd_secret_move2() -{ - if (self.noise2 != "") - sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM); - SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3); -} - -// Wait here until time to go back... -void fd_secret_move3() -{ - if (self.noise3 != "") - sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM); - if (!(self.spawnflags & SECRET_OPEN_ONCE)) - { - self.nextthink = self.ltime + self.wait; - self.think = fd_secret_move4; - } -} - -// Move backward... -void fd_secret_move4() -{ - if (self.noise2 != "") - sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM); - SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5); -} - -// Wait 1 second... -void fd_secret_move5() -{ - self.nextthink = self.ltime + 1.0; - self.think = fd_secret_move6; - if (self.noise3 != "") - sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM); -} - -void fd_secret_move6() -{ - if (self.noise2 != "") - sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM); - SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done); -} - -void fd_secret_done() -{ - if (self.spawnflags&SECRET_YES_SHOOT) - { - self.health = 10000; - self.takedamage = DAMAGE_YES; - //self.th_pain = fd_secret_use; - } - if (self.noise3 != "") - sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM); -} - -void secret_blocked() -{ - if (time < self.attack_finished_single) - return; - self.attack_finished_single = time + 0.5; - //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic); -} - -/* -============== -secret_touch - -Prints messages -================ -*/ -void secret_touch() -{ - if (!other.iscreature) - return; - if (self.attack_finished_single > time) - return; - - self.attack_finished_single = time + 2; - - if (self.message) - { - if (IS_CLIENT(other)) - centerprint(other, self.message); - play2(other, "misc/talk.wav"); - } -} - -void secret_reset() -{ - if (self.spawnflags&SECRET_YES_SHOOT) - { - self.health = 10000; - self.takedamage = DAMAGE_YES; - } - setorigin(self, self.oldorigin); - self.think = func_null; - self.nextthink = 0; -} - -/*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot -Basic secret door. Slides back, then to the side. Angle determines direction. -wait = # of seconds before coming back -1st_left = 1st move is left of arrow -1st_down = 1st move is down from arrow -always_shoot = even if targeted, keep shootable -t_width = override WIDTH to move back (or height if going down) -t_length = override LENGTH to move sideways -"dmg" damage to inflict when blocked (2 default) - -If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage. -"sounds" -1) medieval -2) metal -3) base -*/ - -void spawnfunc_func_door_secret() -{ - /*if (!self.deathtype) // map makers can override this - self.deathtype = " got in the way";*/ - - if (!self.dmg) - self.dmg = 2; - - // Magic formula... - self.mangle = self.angles; - self.angles = '0 0 0'; - self.classname = "door"; - if (!InitMovingBrushTrigger()) - return; - self.effects |= EF_LOWPRECISION; - - self.touch = secret_touch; - self.blocked = secret_blocked; - self.speed = 50; - self.use = fd_secret_use; - IFTARGETED - { - } - else - self.spawnflags |= SECRET_YES_SHOOT; - - if(self.spawnflags&SECRET_YES_SHOOT) - { - self.health = 10000; - self.takedamage = DAMAGE_YES; - self.event_damage = fd_secret_damage; - } - self.oldorigin = self.origin; - if (!self.wait) - self.wait = 5; // 5 seconds before closing - - self.reset = secret_reset; - secret_reset(); -} - -/*QUAKED spawnfunc_func_fourier (0 .5 .8) ? -Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions. -netname: list of quadruples, separated by spaces; note that phase 0 represents a sine wave, and phase 0.25 a cosine wave (by default, it uses 1 0 0 0 1, to match func_bobbing's defaults -speed: how long one cycle of frequency multiplier 1 in seconds (default 4) -height: amplitude modifier (default 32) -phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0) -noise: path/name of looping .wav file to play. -dmg: Do this mutch dmg every .dmgtime intervall when blocked -dmgtime: See above. -*/ - -void func_fourier_controller_think() -{ - vector v; - float n, i, t; - - self.nextthink = time + 0.1; - if(self.owner.active != ACTIVE_ACTIVE) - { - self.owner.velocity = '0 0 0'; - return; - } - - - n = floor((tokenize_console(self.owner.netname)) / 5); - t = self.nextthink * self.owner.cnt + self.owner.phase * 360; - - v = self.owner.destvec; - - for(i = 0; i < n; ++i) - { - makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0'); - v = v + ('1 0 0' * stof(argv(i*5+2)) + '0 1 0' * stof(argv(i*5+3)) + '0 0 1' * stof(argv(i*5+4))) * self.owner.height * v_forward.y; - } - - if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed - // * 10 so it will arrive in 0.1 sec - self.owner.velocity = (v - self.owner.origin) * 10; -} - -void spawnfunc_func_fourier() -{ - entity controller; - if (self.noise != "") - { - precache_sound(self.noise); - soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE); - } - - if (!self.speed) - self.speed = 4; - if (!self.height) - self.height = 32; - self.destvec = self.origin; - self.cnt = 360 / self.speed; - - self.blocked = generic_plat_blocked; - if(self.dmg && (self.message == "")) - self.message = " was squished"; - if(self.dmg && (self.message2 == "")) - self.message2 = "was squished by"; - if(self.dmg && (!self.dmgtime)) - self.dmgtime = 0.25; - self.dmgtime2 = time; - - if(self.netname == "") - self.netname = "1 0 0 0 1"; - - if (!InitMovingBrushTrigger()) - return; - - self.active = ACTIVE_ACTIVE; - - // wait for targets to spawn - controller = spawn(); - controller.classname = "func_fourier_controller"; - controller.owner = self; - controller.nextthink = time + 1; - controller.think = func_fourier_controller_think; - self.nextthink = self.ltime + 999999999; - self.think = SUB_NullThink; // for PushMove - - // Savage: Reduce bandwith, critical on e.g. nexdm02 - self.effects |= EF_LOWPRECISION; - - // TODO make a reset function for this one -} - -// reusing some fields havocbots declared -.entity wp00, wp01, wp02, wp03; - -.float targetfactor, target2factor, target3factor, target4factor; -.vector targetnormal, target2normal, target3normal, target4normal; - -vector func_vectormamamam_origin(entity o, float t) -{ - vector v, p; - float f; - entity e; - - f = o.spawnflags; - v = '0 0 0'; - - e = o.wp00; - if(e) - { - p = e.origin + t * e.velocity; - if(f & 1) - v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor; - else - v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor; - } - - e = o.wp01; - if(e) - { - p = e.origin + t * e.velocity; - if(f & 2) - v = v + (p * o.target2normal) * o.target2normal * o.target2factor; - else - v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor; - } - - e = o.wp02; - if(e) - { - p = e.origin + t * e.velocity; - if(f & 4) - v = v + (p * o.target3normal) * o.target3normal * o.target3factor; - else - v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor; - } - - e = o.wp03; - if(e) - { - p = e.origin + t * e.velocity; - if(f & 8) - v = v + (p * o.target4normal) * o.target4normal * o.target4factor; - else - v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor; - } - - return v; -} - -void func_vectormamamam_controller_think() -{ - self.nextthink = time + 0.1; - - if(self.owner.active != ACTIVE_ACTIVE) - { - self.owner.velocity = '0 0 0'; - return; - } - - if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed - self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10; -} - -void func_vectormamamam_findtarget() -{ - if(self.target != "") - self.wp00 = find(world, targetname, self.target); - - if(self.target2 != "") - self.wp01 = find(world, targetname, self.target2); - - if(self.target3 != "") - self.wp02 = find(world, targetname, self.target3); - - if(self.target4 != "") - self.wp03 = find(world, targetname, self.target4); - - if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03) - objerror("No reference entity found, so there is nothing to move. Aborting."); - - self.destvec = self.origin - func_vectormamamam_origin(self, 0); - - entity controller; - controller = spawn(); - controller.classname = "func_vectormamamam_controller"; - controller.owner = self; - controller.nextthink = time + 1; - controller.think = func_vectormamamam_controller_think; -} - -void spawnfunc_func_vectormamamam() -{ - if (self.noise != "") - { - precache_sound(self.noise); - soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE); - } - - if(!self.targetfactor) - self.targetfactor = 1; - - if(!self.target2factor) - self.target2factor = 1; - - if(!self.target3factor) - self.target3factor = 1; - - if(!self.target4factor) - self.target4factor = 1; - - if(vlen(self.targetnormal)) - self.targetnormal = normalize(self.targetnormal); - - if(vlen(self.target2normal)) - self.target2normal = normalize(self.target2normal); - - if(vlen(self.target3normal)) - self.target3normal = normalize(self.target3normal); - - if(vlen(self.target4normal)) - self.target4normal = normalize(self.target4normal); - - self.blocked = generic_plat_blocked; - if(self.dmg && (self.message == "")) - self.message = " was squished"; - if(self.dmg && (self.message == "")) - self.message2 = "was squished by"; - if(self.dmg && (!self.dmgtime)) - self.dmgtime = 0.25; - self.dmgtime2 = time; - - if(self.netname == "") - self.netname = "1 0 0 0 1"; - - if (!InitMovingBrushTrigger()) - return; - - // wait for targets to spawn - self.nextthink = self.ltime + 999999999; - self.think = SUB_NullThink; // for PushMove - - // Savage: Reduce bandwith, critical on e.g. nexdm02 - self.effects |= EF_LOWPRECISION; - - self.active = ACTIVE_ACTIVE; - - InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET); -} - -void conveyor_think() -{ - entity e; - - // set myself as current conveyor where possible - for(e = world; (e = findentity(e, conveyor, self)); ) - e.conveyor = world; - - if(self.state) - { - for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain) - if(!e.conveyor.state) - if(isPushable(e)) - { - vector emin = e.absmin; - vector emax = e.absmax; - if(self.solid == SOLID_BSP) - { - emin -= '1 1 1'; - emax += '1 1 1'; - } - if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick - if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate - e.conveyor = self; - } - - for(e = world; (e = findentity(e, conveyor, self)); ) - { - if(IS_CLIENT(e)) // doing it via velocity has quite some advantages - continue; // done in SV_PlayerPhysics - - setorigin(e, e.origin + self.movedir * sys_frametime); - move_out_of_solid(e); - UpdateCSQCProjectile(e); - /* - // stupid conveyor code - tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e); - if(trace_fraction > 0) - setorigin(e, trace_endpos); - */ - } - } - - self.nextthink = time; -} - -void conveyor_use() -{ - self.state = !self.state; -} - -void conveyor_reset() -{ - self.state = (self.spawnflags & 1); -} - -void conveyor_init() -{ - if (!self.speed) - self.speed = 200; - self.movedir = self.movedir * self.speed; - self.think = conveyor_think; - self.nextthink = time; - IFTARGETED - { - self.use = conveyor_use; - self.reset = conveyor_reset; - conveyor_reset(); - } - else - self.state = 1; -} - -void spawnfunc_trigger_conveyor() -{ - SetMovedir(); - EXACTTRIGGER_INIT; - conveyor_init(); -} - -void spawnfunc_func_conveyor() -{ - SetMovedir(); - InitMovingBrushTrigger(); - self.movetype = MOVETYPE_NONE; - conveyor_init(); -} diff --git a/qcsrc/server/target_music.qc b/qcsrc/server/target_music.qc deleted file mode 100644 index 681a4ca6d..000000000 --- a/qcsrc/server/target_music.qc +++ /dev/null @@ -1,137 +0,0 @@ -#include "_all.qh" - -.float volume; -.float lifetime; -// values: -// volume -// noise -// targetname -// lifetime -// fade_time -// fade_rate -// when triggered, the music is overridden for activator until lifetime (or forever, if lifetime is 0) -// when targetname is not set, THIS ONE is default -void target_music_sendto(float to, float is) -{ - WriteByte(to, SVC_TEMPENTITY); - WriteByte(to, TE_CSQC_TARGET_MUSIC); - WriteShort(to, num_for_edict(self)); - WriteByte(to, self.volume * 255.0 * is); - WriteByte(to, self.fade_time * 16.0); - WriteByte(to, self.fade_rate * 16.0); - WriteByte(to, self.lifetime); - WriteString(to, self.noise); -} -void target_music_reset() -{ - if(self.targetname == "") - target_music_sendto(MSG_ALL, 1); -} -void target_music_use() -{ - if(!activator) - return; - if(IS_REAL_CLIENT(activator)) - { - msg_entity = activator; - target_music_sendto(MSG_ONE, 1); - } - entity head; - FOR_EACH_SPEC(head) if(head.enemy == activator) { msg_entity = head; target_music_sendto(MSG_ONE, 1); } -} -void spawnfunc_target_music() -{ - self.use = target_music_use; - self.reset = target_music_reset; - if(!self.volume) - self.volume = 1; - if(self.targetname == "") - target_music_sendto(MSG_INIT, 1); - else - target_music_sendto(MSG_INIT, 0); -} -void TargetMusic_RestoreGame() -{ - for(self = world; (self = find(self, classname, "target_music")); ) - { - if(self.targetname == "") - target_music_sendto(MSG_INIT, 1); - else - target_music_sendto(MSG_INIT, 0); - } -} -// values: -// volume -// noise -// targetname -// fade_time -// spawnflags: -// 1 = START_OFF -// when triggered, it is disabled/enabled for everyone -float trigger_music_SendEntity(entity to, int sf) -{ - WriteByte(MSG_ENTITY, ENT_CLIENT_TRIGGER_MUSIC); - sf &= ~0x80; - if(self.cnt) - sf |= 0x80; - WriteByte(MSG_ENTITY, sf); - if(sf & 4) - { - WriteCoord(MSG_ENTITY, self.origin.x); - WriteCoord(MSG_ENTITY, self.origin.y); - WriteCoord(MSG_ENTITY, self.origin.z); - } - if(sf & 1) - { - if(self.model != "null") - { - WriteShort(MSG_ENTITY, self.modelindex); - WriteCoord(MSG_ENTITY, self.mins.x); - WriteCoord(MSG_ENTITY, self.mins.y); - WriteCoord(MSG_ENTITY, self.mins.z); - WriteCoord(MSG_ENTITY, self.maxs.x); - WriteCoord(MSG_ENTITY, self.maxs.y); - WriteCoord(MSG_ENTITY, self.maxs.z); - } - else - { - WriteShort(MSG_ENTITY, 0); - WriteCoord(MSG_ENTITY, self.maxs.x); - WriteCoord(MSG_ENTITY, self.maxs.y); - WriteCoord(MSG_ENTITY, self.maxs.z); - } - WriteByte(MSG_ENTITY, self.volume * 255.0); - WriteByte(MSG_ENTITY, self.fade_time * 16.0); - WriteByte(MSG_ENTITY, self.fade_rate * 16.0); - WriteString(MSG_ENTITY, self.noise); - } - return 1; -} -void trigger_music_reset() -{ - self.cnt = !(self.spawnflags & 1); - self.SendFlags |= 0x80; -} -void trigger_music_use() -{ - self.cnt = !self.cnt; - self.SendFlags |= 0x80; -} -void spawnfunc_trigger_music() -{ - if(self.model != "") - setmodel(self, self.model); - if(!self.volume) - self.volume = 1; - if(!self.modelindex) - { - setorigin(self, self.origin + self.mins); - setsize(self, '0 0 0', self.maxs - self.mins); - } - trigger_music_reset(); - - self.use = trigger_music_use; - self.reset = trigger_music_reset; - - Net_LinkEntity(self, false, 0, trigger_music_SendEntity); -} -- 2.39.2