From e4c6c6d7f00f23fe23a2145278a15c308e70ec28 Mon Sep 17 00:00:00 2001 From: bones_was_here Date: Fri, 4 Aug 2023 18:06:31 +0000 Subject: [PATCH] func_door and func_plat fixes and Q3 compatibility --- qcsrc/common/mapobjects/func/door.qc | 139 ++++++++++++++++++--------- qcsrc/common/mapobjects/func/door.qh | 3 +- qcsrc/common/mapobjects/func/plat.qc | 34 +++++-- qcsrc/common/mapobjects/platforms.qc | 12 ++- qcsrc/server/main.qc | 26 +++++ 5 files changed, 160 insertions(+), 54 deletions(-) diff --git a/qcsrc/common/mapobjects/func/door.qc b/qcsrc/common/mapobjects/func/door.qc index 1316c3283..9751719ce 100644 --- a/qcsrc/common/mapobjects/func/door.qc +++ b/qcsrc/common/mapobjects/func/door.qc @@ -29,6 +29,7 @@ void door_blocked(entity this, entity blocker) { bool reverse = false; if((this.spawnflags & DOOR_CRUSH) + && !Q3COMPAT_COMMON #ifdef SVQC && (blocker.takedamage != DAMAGE_NO) #elif defined(CSQC) @@ -43,46 +44,46 @@ void door_blocked(entity this, entity blocker) else { #ifdef SVQC - if((this.dmg) && (blocker.takedamage == DAMAGE_YES)) // Shall we bite? + if(this.dmg && blocker.takedamage != DAMAGE_NO) // Shall we bite? Damage (blocker, this, this, this.dmg, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); #endif - // don't change direction for dead or dying stuff - if(IS_DEAD(blocker) + // don't change direction for dead or dying stuff + if(!IS_DEAD(blocker) #ifdef SVQC - && (blocker.takedamage == DAMAGE_NO) + && blocker.takedamage != DAMAGE_NO #endif + && this.wait >= 0 + && !(Q3COMPAT_COMMON && (this.spawnflags & Q3_DOOR_CRUSHER)) ) { - if (this.wait >= 0) + if (this.state == STATE_DOWN) { - if (this.state == STATE_DOWN) - { - if (this.classname == "door") - door_go_up(this, NULL, NULL); - else - door_rotating_go_up(this, blocker); - } + if (this.classname == "door") + door_go_up(this, NULL, NULL); else - { - if (this.classname == "door") - door_go_down(this); - else - door_rotating_go_down(this); - } - reverse = true; + door_rotating_go_up(this, blocker); } + else + { + if (this.classname == "door") + door_go_down(this); + else + door_rotating_go_down(this); + } + reverse = true; } #ifdef SVQC else { //gib dying stuff just to make sure - if((this.dmg) && (blocker.takedamage != DAMAGE_NO)) // Shall we bite? + if(this.dmg && blocker.takedamage != DAMAGE_NO && IS_DEAD(blocker)) // Shall we bite? Damage (blocker, this, this, 10000, DEATH_HURTTRIGGER.m_id, DMG_NOWEP, blocker.origin, '0 0 0'); } #endif } - if (!reverse && this.classname == "door") + // if we didn't change direction and are using a non-linear movement controller, we must pause it + if (!reverse && this.classname == "door" && this.move_controller) SUB_CalcMovePause(this); } @@ -212,10 +213,13 @@ bool door_check_keys(entity door, entity player) return false; } -void door_fire(entity this, entity actor, entity trigger) +void door_use(entity this, entity actor, entity trigger) { - if (this.owner != this) - objerror (this, "door_fire: this.owner != this"); + //dprint("door_use (model: ");dprint(this.model);dprint(")\n"); + + if (!this.owner) + return; + this = this.owner; if (this.spawnflags & DOOR_TOGGLE) { @@ -256,14 +260,6 @@ void door_fire(entity this, entity actor, entity trigger) } while ((e != this) && (e != NULL)); } -void door_use(entity this, entity actor, entity trigger) -{ - //dprint("door_use (model: ");dprint(this.model);dprint(")\n"); - - if (this.owner) - door_fire(this.owner, actor, trigger); -} - void door_damage(entity this, entity inflictor, entity attacker, float damage, int deathtype, .entity weaponentity, vector hitloc, vector force) { if(this.spawnflags & NOSPLASH) @@ -369,14 +365,25 @@ void door_trigger_touch(entity this, entity toucher) #endif return; - if (time < this.door_finished) + if (this.owner.state == STATE_UP) return; // check if door is locked if (!door_check_keys(this, toucher)) return; - this.door_finished = time + 1; + if (this.owner.state == STATE_TOP) + { + if (this.owner.nextthink < this.owner.ltime + this.owner.wait) + { + entity e = this.owner; + do { + e.nextthink = e.ltime + e.wait; + e = e.enemy; + } while (e != this.owner); + } + return; + } door_use(this.owner, toucher, NULL); } @@ -408,7 +415,9 @@ LinkDoors entity LinkDoors_nextent(entity cur, entity near, entity pass) { - while((cur = find(cur, classname, pass.classname)) && ((cur.spawnflags & DOOR_DONT_LINK) || cur.enemy)) + while((cur = find(cur, classname, pass.classname)) + && (((cur.spawnflags & DOOR_DONT_LINK) && !Q3COMPAT_COMMON) + || cur.enemy)) { } return cur; @@ -416,6 +425,9 @@ entity LinkDoors_nextent(entity cur, entity near, entity pass) bool LinkDoors_isconnected(entity e1, entity e2, entity pass) { + if(Q3COMPAT_COMMON) + return e1.team == e2.team; + float DELTA = 4; if((e1.absmin_x > e2.absmax_x + DELTA) || (e1.absmin_y > e2.absmax_y + DELTA) @@ -441,7 +453,10 @@ void LinkDoors(entity this) if (this.enemy) return; // already linked by another door - if (this.spawnflags & DOOR_DONT_LINK) + + // Q3 door linking is done for teamed doors only and is not affected by spawnflags or bmodel proximity + if (((this.spawnflags & DOOR_DONT_LINK) && !Q3COMPAT_COMMON) + || (Q3COMPAT_COMMON && !this.team)) { this.owner = this.enemy = this; @@ -544,7 +559,7 @@ SILVER_KEY causes the door to open only if the activator holds a silver key. "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) +"dmg" damage to inflict when blocked (0 default) "sounds" 0) no sound 1) stone @@ -609,9 +624,12 @@ void door_init_startopen(entity this) this.pos2 = this.pos1; this.pos1 = this.origin; +// no longer needed: not using delayed initialisation for door_init_startopen() +#if 0 #ifdef SVQC this.SendFlags |= SF_TRIGGER_UPDATE; #endif +#endif } void door_reset(entity this) @@ -666,14 +684,28 @@ void door_init_shared(entity this) if (q3compat) { - // CPMA adds these fields for overriding the engine sounds + // CPMA adds these fields for overriding the Q3 default sounds string s = GetField_fullspawndata(this, "sound_start", true); string e = GetField_fullspawndata(this, "sound_end", true); if (s) this.noise2 = strzone(s); + else + { + // PK3s supporting Q3A sometimes include custom sounds at Q3 default paths + s = "sound/movers/doors/dr1_strt.wav"; + if (FindFileInMapPack(s)) + this.noise2 = s; + } + if (e) this.noise1 = strzone(e); + else + { + e = "sound/movers/doors/dr1_end.wav"; + if (FindFileInMapPack(e)) + this.noise1 = e; + } } // sound when door stops moving @@ -693,7 +725,7 @@ void door_init_shared(entity this) } else if (!this.wait) { - this.wait = 3; + this.wait = q3compat ? 2 : 3; } if (!this.lip) @@ -738,15 +770,19 @@ spawnfunc(func_door) if(this.spawnflags & DOOR_NONSOLID) this.solid = SOLID_NOT; -// DOOR_START_OPEN is to allow an entity to be lighted in the closed position -// but spawn in the open position - if (this.spawnflags & DOOR_START_OPEN) - InitializeEntity(this, door_init_startopen, INITPRIO_SETLOCATION); - door_init_shared(this); this.pos1 = this.origin; - this.pos2 = this.pos1 + this.movedir*(fabs(this.movedir*this.size) - this.lip); + vector absmovedir; + absmovedir.x = fabs(this.movedir.x); + absmovedir.y = fabs(this.movedir.y); + absmovedir.z = fabs(this.movedir.z); + this.pos2 = this.pos1 + this.movedir * (absmovedir * this.size - this.lip); + +// DOOR_START_OPEN is to allow an entity to be lighted in the closed position +// but spawn in the open position + if (this.spawnflags & DOOR_START_OPEN) + door_init_startopen(this); if(autocvar_sv_doors_always_open) { @@ -760,6 +796,19 @@ spawnfunc(func_door) this.speed = 100; } + if (q3compat) + { + if (!this.dmg) + this.dmg = 2; + + if (!this.team) + { + string t = GetField_fullspawndata(this, "team"); + // bones_was_here: same hack as used to support teamed items on Q3 maps + if(t) this.team = crc16(false, t); + } + } + settouch(this, door_touch); // LinkDoors can't be done until all of the doors have been spawned, so diff --git a/qcsrc/common/mapobjects/func/door.qh b/qcsrc/common/mapobjects/func/door.qh index f185f4be8..ce7025dd5 100644 --- a/qcsrc/common/mapobjects/func/door.qh +++ b/qcsrc/common/mapobjects/func/door.qh @@ -4,7 +4,7 @@ bool autocvar_sv_doors_always_open; #endif -const int DOOR_START_OPEN = BIT(0); +const int DOOR_START_OPEN = BIT(0); // has same meaning in Q3: reverse position 1 and 2 const int DOOR_DONT_LINK = BIT(2); const int SPAWNFLAGS_GOLD_KEY = BIT(3); // Quake 1 compat, can only be used with func_door! const int SPAWNFLAGS_SILVER_KEY = BIT(4); // Quake 1 compat, can only be used with func_door! @@ -13,6 +13,7 @@ const int DOOR_TOGGLE = BIT(5); const int DOOR_NONSOLID = BIT(10); const int DOOR_CRUSH = BIT(11); // can't use CRUSH cause that is the same as DOOR_DONT_LINK +#define Q3_DOOR_CRUSHER BIT(2) // in Q3 this disables the auto reverse so the blocking player takes damage every frame #ifdef CSQC // stuff for preload diff --git a/qcsrc/common/mapobjects/func/plat.qc b/qcsrc/common/mapobjects/func/plat.qc index 2376c5956..08faae9eb 100644 --- a/qcsrc/common/mapobjects/func/plat.qc +++ b/qcsrc/common/mapobjects/func/plat.qc @@ -7,7 +7,9 @@ void plat_link(entity this); void plat_delayedinit(entity this) { plat_link(this); - plat_spawn_inside_trigger(this); // the "start moving" trigger + // Q3 uses only a truth check of .targetname to decide whether to spawn a trigger + if(!Q3COMPAT_COMMON || this.targetname == "") + plat_spawn_inside_trigger(this); // the "start moving" trigger } float plat_send(entity this, entity to, float sf) @@ -56,7 +58,12 @@ void plat_link(entity this) spawnfunc(func_plat) { - if (this.spawnflags & CRUSH) + if (q3compat) + { + this.spawnflags = 0; // Q3 plats have no spawnflags + if (!this.dmg) this.dmg = 2; + } + else if (this.spawnflags & CRUSH) { this.dmg = 10000; } @@ -91,14 +98,28 @@ spawnfunc(func_plat) if (q3compat) { - // CPMA adds these fields for overriding the engine sounds + // CPMA adds these fields for overriding the Q3 default sounds string s = GetField_fullspawndata(this, "sound_start", true); string e = GetField_fullspawndata(this, "sound_end", true); if (s) this.noise = strzone(s); + else + { + // PK3s supporting Q3A sometimes include custom sounds at Q3 default paths + s = "sound/movers/plats/pt1_strt.wav"; + if (FindFileInMapPack(s)) + this.noise = s; + } + if (e) this.noise1 = strzone(e); + else + { + e = "sound/movers/plats/pt1_end.wav"; + if (FindFileInMapPack(e)) + this.noise1 = e; + } } if(this.noise && this.noise != "") @@ -122,8 +143,8 @@ spawnfunc(func_plat) setblocked(this, plat_crush); - if (!this.speed) this.speed = 150; - if (!this.lip) this.lip = 16; + if (!this.speed) this.speed = q3compat ? 200 : 150; + if (!this.lip) this.lip = q3compat ? 8 : 16; if (!this.height) this.height = this.size.z - this.lip; this.pos1 = this.origin; @@ -186,7 +207,8 @@ NET_HANDLE(ENT_CLIENT_PLAT, bool isnew) set_movetype(this, MOVETYPE_PUSH); this.move_time = time; - plat_spawn_inside_trigger(this); + if(!Q3COMPAT_COMMON || this.targetname == "") + plat_spawn_inside_trigger(this); } if(sf & SF_TRIGGER_RESET) diff --git a/qcsrc/common/mapobjects/platforms.qc b/qcsrc/common/mapobjects/platforms.qc index 28b420b20..9e1f635b3 100644 --- a/qcsrc/common/mapobjects/platforms.qc +++ b/qcsrc/common/mapobjects/platforms.qc @@ -173,13 +173,21 @@ void plat_use(entity this, entity actor, entity trigger) plat_go_down(this); } +void plat_target_use(entity this, entity actor, entity trigger) +{ + if (this.state == STATE_TOP) + this.nextthink = this.ltime + 1; + else if (this.state != STATE_UP) + plat_go_up(this); +} + // WARNING: backwards compatibility because people don't use already existing fields :( // TODO: Check if any maps use these fields and remove these fields if it doesn't break maps .string sound1, sound2; void plat_reset(entity this) { - if(this.targetname && this.targetname != "") + if(this.targetname && this.targetname != "" && !Q3COMPAT_COMMON) { setorigin(this, this.pos1); this.state = STATE_UP; @@ -189,7 +197,7 @@ void plat_reset(entity this) { setorigin(this, this.pos2); this.state = STATE_BOTTOM; - this.use = plat_trigger_use; + this.use = (this.targetname != "" && Q3COMPAT_COMMON) ? plat_target_use : plat_trigger_use; } #ifdef SVQC diff --git a/qcsrc/server/main.qc b/qcsrc/server/main.qc index 0d36ea47e..40f4d6c2f 100644 --- a/qcsrc/server/main.qc +++ b/qcsrc/server/main.qc @@ -468,6 +468,32 @@ string GetField_fullspawndata(entity e, string f, ...) return v; } +/* +============= +FindFileInMapPack + +Returns the first matching VFS file path that exists in the current map's pack. +Returns string_null if no files match or the map isn't packaged. +============= +*/ +string FindFileInMapPack(string pattern) +{ + if(!checkextension("DP_QC_FS_SEARCH_PACKFILE")) + return string_null; + + string base_pack = whichpack(strcat("maps/", mapname, ".bsp")); + if(base_pack == "" || !base_pack) // this map isn't packaged or there was an error + return string_null; + + int glob = search_packfile_begin(pattern, true, true, base_pack); + if(glob < 0) + return string_null; + + string file = search_getfilename(glob, 0); + search_end(glob); + return file; +} + void WarpZone_PostInitialize_Callback() { // create waypoint links for warpzones -- 2.39.2