Skip to content

Commit fbceca7

Browse files
committed
player-medic revived monster levels are now based on the level of the corpse instead of the medic's level; however, a bonus is applied based on the medic's level (currently 5% per level). the revived monster will die after a set amount of time and must be revived again.
1 parent f7b1995 commit fbceca7

9 files changed

Lines changed: 108 additions & 22 deletions

File tree

lua/variables.lua

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,9 @@ MEDIC_BOLT_INITIAL_SPEED = 1500
829829
MEDIC_BOLT_ADDON_SPEED = 0
830830
MEDIC_BOLT_AMMO = 10
831831
MEDIC_RESURRECT_DELAY = 1.0
832-
MEDIC_RESURRECT_BONUS = 0.25
832+
MEDIC_RESURRECT_BONUS = 0.05
833+
MEDIC_RESURRECT_BASE = 1.0
834+
MEDIC_RESURRECT_TIMEOUT = 180.0
833835

834836
-- Player Tank
835837
P_TANK_PUNCH_RADIUS = 128

src/characters/settings.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,8 @@ extern double MEDIC_BOLT_ADDON_SPEED;
848848
extern double MEDIC_BOLT_AMMO;
849849
extern double MEDIC_RESURRECT_DELAY;
850850
extern double MEDIC_RESURRECT_BONUS;
851+
extern double MEDIC_RESURRECT_BASE;
852+
extern double MEDIC_RESURRECT_TIMEOUT;
851853
extern double P_TANK_PUNCH_RADIUS;
852854
extern double P_TANK_PUNCH_INITIAL_DMG;
853855
extern double P_TANK_PUNCH_ADDON_DMG;

src/combat/abilities/gloom.c

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,11 @@ void organ_touch (edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *sur
207207
void organ_death_message(edict_t* self, edict_t* attacker, int max)
208208
{
209209
int qty;
210+
211+
// don't need to print a message if the revived organ expired
212+
if (attacker && attacker->inuse && attacker == world && level.time >= self->monsterinfo.resurrected_timeout)
213+
return;
214+
210215
if (self->mtype == M_SPIKER)
211216
qty = self->activator->num_spikers;
212217
else if (self->mtype == M_GASSER)
@@ -315,6 +320,8 @@ void organ_remove (edict_t *self, qboolean refund)
315320
self->deadflag = DEAD_DEAD;
316321
}
317322

323+
// removes organ if activator becomes invalid
324+
// returns false if the organ was removed, true if not
318325
qboolean organ_checkowner (edict_t *self)
319326
{
320327
qboolean remove = false;
@@ -337,6 +344,22 @@ qboolean organ_checkowner (edict_t *self)
337344
return true;
338345
}
339346

347+
// kills organ if it expires
348+
// return false if the organ died, true if not
349+
qboolean organ_checkrevived(edict_t* self)
350+
{
351+
//gi.dprintf("%s: %d %.1f %.1f\n", __func__, self->monsterinfo.resurrected_level, self->monsterinfo.resurrected_timeout, (float)level.time);
352+
// resurrected organ has expired
353+
if (self->monsterinfo.resurrected_level && level.time > self->monsterinfo.resurrected_timeout)
354+
{
355+
// kill it!
356+
self->health = 0;
357+
self->die(self, world, world, 0, vec3_origin);
358+
return false;
359+
}
360+
return true;
361+
}
362+
340363
// used to restore a prevous movetype (MOVETYPE_TOSS or MOVETYPE_NONE) needed for V_Push to work (MOVETYPE_STEP)
341364
void organ_restoreMoveType (edict_t *self)
342365
{
@@ -879,7 +902,7 @@ void spiker_think (edict_t *self)
879902
{
880903
edict_t *e=NULL;
881904

882-
if (!organ_checkowner(self))
905+
if (!organ_checkowner(self) || !organ_checkrevived(self))
883906
return;
884907

885908
vrx_set_pickup_owner(self); // sets owner when this entity is picked up, making it non-solid to the player
@@ -972,7 +995,7 @@ void spiker_think (edict_t *self)
972995

973996
void spiker_grow (edict_t *self)
974997
{
975-
if (!organ_checkowner(self))
998+
if (!organ_checkowner(self) || !organ_checkrevived(self))
976999
return;
9771000

9781001
vrx_set_pickup_owner(self); // sets owner when this entity is picked up, making it non-solid to the player
@@ -1397,7 +1420,7 @@ void obstacle_attack(edict_t* self)
13971420

13981421
void obstacle_think (edict_t *self)
13991422
{
1400-
if (!organ_checkowner(self))
1423+
if (!organ_checkowner(self) || !organ_checkrevived(self))
14011424
return;
14021425

14031426
vrx_set_pickup_owner(self); // sets owner when this entity is picked up, making it non-solid to the player
@@ -1423,7 +1446,7 @@ void obstacle_think (edict_t *self)
14231446

14241447
void obstacle_grow (edict_t *self)
14251448
{
1426-
if (!organ_checkowner(self))
1449+
if (!organ_checkowner(self) || !organ_checkrevived(self))
14271450
return;
14281451
//gi.dprintf("movetype: %d pitch: %.0f\n", self->movetype, self->s.angles[PITCH]);
14291452
//organ_restoreMoveType(self);
@@ -2158,7 +2181,7 @@ qboolean gasser_findtarget (edict_t *self)
21582181

21592182
void gasser_think (edict_t *self)
21602183
{
2161-
if (!organ_checkowner(self))
2184+
if (!organ_checkowner(self) || !organ_checkrevived(self))
21622185
return;
21632186

21642187
vrx_set_pickup_owner(self); // sets owner when this entity is picked up, making it non-solid to the player
@@ -2224,7 +2247,7 @@ void gasser_think (edict_t *self)
22242247

22252248
void gasser_grow (edict_t *self)
22262249
{
2227-
if (!organ_checkowner(self))
2250+
if (!organ_checkowner(self) || !organ_checkrevived(self))
22282251
return;
22292252

22302253
vrx_set_pickup_owner(self); // sets owner when this entity is picked up, making it non-solid to the player
@@ -2705,6 +2728,9 @@ void cocoon_think (edict_t *self)
27052728
return;
27062729
}
27072730

2731+
if (!organ_checkrevived(self))
2732+
return;
2733+
27082734
cocoon_attack(self);
27092735

27102736
if (level.time > self->lasthurt + 1.0)

src/combat/abilities/playermonster/playertomedic.c

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,35 @@ qboolean p_medic_healframe (edict_t *ent)
3535
}
3636

3737
edict_t *CreateSpiker (edict_t *ent, int skill_level);
38-
edict_t *CreateObstacle (edict_t *ent, int skill_level);
38+
edict_t* CreateObstacle(edict_t* ent, int skill_level, int talent_level);
3939
edict_t* CreateGasser(edict_t* ent, int skill_level, int talent_level);
4040
edict_t *CreateCocoon (edict_t *ent, int skill_level);
4141
edict_t* CreateHealer(edict_t* ent, int skill_level);
4242

4343
void p_medic_reanimate (edict_t *ent, edict_t *target)
4444
{
45+
int res_level;
4546
int skill_level;
47+
float skill_bonus;
4648
vec3_t bmin, bmax;
4749
edict_t *e;
4850

49-
skill_level = floattoint((1.0+MEDIC_RESURRECT_BONUS)*ent->myskills.abilities[MEDIC].current_level);
51+
// calculate skill bonus from medic
52+
skill_bonus = MEDIC_RESURRECT_BASE + MEDIC_RESURRECT_BONUS * ent->myskills.abilities[MEDIC].current_level;
53+
// save the original monster's level so we don't apply the bonus more than once
54+
if (!target->monsterinfo.resurrected_level)
55+
res_level = target->monsterinfo.resurrected_level = target->monsterinfo.level;
56+
else
57+
res_level = target->monsterinfo.resurrected_level;
58+
// calculate skill level of resurrected monster with bonus applied
59+
skill_level = floattoint(target->monsterinfo.resurrected_level * skill_bonus);
60+
//target->monsterinfo.level = skill_level; // set level with resurrect bonus
5061

5162
if (!strcmp(target->classname, "drone")
5263
&& (ent->num_monsters + target->monsterinfo.control_cost <= MAX_MONSTERS)
5364
&& !(target->flags & FL_UNDEAD) && target->mtype != M_DECOY && target->mtype != M_GOLEM) // don't revive decoys, golem, and undead (skeletons)
5465
{
55-
target->monsterinfo.level = skill_level;
66+
target->monsterinfo.level = skill_level; // set level with resurrect bonus
5667
M_SetBoundingBox(target->mtype, bmin, bmax);
5768

5869
if (G_IsValidLocation(target, target->s.origin, bmin, bmax) && M_Initialize(ent, target, 0.0f))
@@ -65,6 +76,7 @@ void p_medic_reanimate (edict_t *ent, edict_t *target)
6576
target->monsterinfo.resurrected_time = level.time + 2.0;
6677
target->activator = ent; // transfer ownership!
6778
target->nextthink = level.time + MEDIC_RESURRECT_DELAY;
79+
target->monsterinfo.resurrected_timeout = target->nextthink + MEDIC_RESURRECT_TIMEOUT; // resurrected monster dies when this timer is exceeded
6880
gi.linkentity(target);
6981
target->monsterinfo.stand(target);
7082

@@ -111,6 +123,7 @@ void p_medic_reanimate (edict_t *ent, edict_t *target)
111123

112124
e->activator = ent;
113125
e->monsterinfo.level = skill_level;
126+
e->monsterinfo.resurrected_level = res_level;
114127
M_Initialize(ent, e, 0.0f);
115128
e->health = 0.2f*e->max_health;
116129
e->monsterinfo.power_armor_power = 0.2f*e->monsterinfo.max_armor;
@@ -134,7 +147,7 @@ void p_medic_reanimate (edict_t *ent, edict_t *target)
134147
VectorCopy(start, e->s.origin);
135148
gi.linkentity(e);
136149
e->nextthink = level.time + MEDIC_RESURRECT_DELAY;
137-
150+
e->monsterinfo.resurrected_timeout = e->nextthink + MEDIC_RESURRECT_TIMEOUT; // resurrected monster dies when this timer is exceeded
138151
ent->num_monsters += e->monsterinfo.control_cost;
139152
ent->num_monsters_real++;
140153
// gi.bprintf(PRINT_HIGH, "adding %p (%d) \n", target, ent->num_monsters_real);
@@ -154,6 +167,8 @@ void p_medic_reanimate (edict_t *ent, edict_t *target)
154167
return;
155168
}
156169

170+
e->monsterinfo.resurrected_level = res_level;
171+
e->monsterinfo.resurrected_timeout = e->nextthink + MEDIC_RESURRECT_TIMEOUT; // resurrected spiker dies when this timer is exceeded
157172
VectorCopy(target->s.angles, e->s.angles);
158173
e->s.angles[PITCH] = 0;
159174
e->monsterinfo.cost = target->monsterinfo.cost;
@@ -168,7 +183,7 @@ void p_medic_reanimate (edict_t *ent, edict_t *target)
168183
}
169184
else if (!strcmp(target->classname, "obstacle") && ent->num_obstacle + 1 <= OBSTACLE_MAX_COUNT)
170185
{
171-
e = CreateObstacle(ent, skill_level);
186+
e = CreateObstacle(ent, skill_level, vrx_get_talent_level(ent, TALENT_MAGNETISM));
172187
// target obstacle is flipped
173188
if (target->s.angles[PITCH] == 180)
174189
{
@@ -191,7 +206,8 @@ void p_medic_reanimate (edict_t *ent, edict_t *target)
191206
return;
192207
}
193208
//gi.dprintf("obstacle will fit\n");
194-
209+
e->monsterinfo.resurrected_level = res_level;
210+
e->monsterinfo.resurrected_timeout = e->nextthink + MEDIC_RESURRECT_TIMEOUT; // resurrected obstacle dies when this timer is exceeded
195211
e->monsterinfo.cost = target->monsterinfo.cost;
196212
e->health = 0.33 * e->max_health;
197213
e->s.frame = 6;
@@ -213,7 +229,8 @@ void p_medic_reanimate (edict_t *ent, edict_t *target)
213229
G_FreeEdict(e);
214230
return;
215231
}
216-
232+
e->monsterinfo.resurrected_level = res_level;
233+
e->monsterinfo.resurrected_timeout = e->nextthink + MEDIC_RESURRECT_TIMEOUT; // resurrected gasser dies when this timer is exceeded
217234
VectorCopy(target->s.angles, e->s.angles);
218235
e->s.angles[PITCH] = 0;
219236
e->monsterinfo.cost = target->monsterinfo.cost;
@@ -238,6 +255,8 @@ void p_medic_reanimate (edict_t *ent, edict_t *target)
238255
return;
239256
}
240257

258+
e->monsterinfo.resurrected_level = res_level;
259+
e->monsterinfo.resurrected_timeout = e->nextthink + MEDIC_RESURRECT_TIMEOUT; // resurrected spiker dies when this timer is exceeded
241260
VectorCopy(target->s.angles, e->s.angles);
242261
e->s.angles[PITCH] = 0;
243262
e->monsterinfo.cost = target->monsterinfo.cost;
@@ -261,6 +280,8 @@ void p_medic_reanimate (edict_t *ent, edict_t *target)
261280
return;
262281
}
263282

283+
e->monsterinfo.resurrected_level = res_level;
284+
e->monsterinfo.resurrected_timeout = e->nextthink + MEDIC_RESURRECT_TIMEOUT; // resurrected healer dies when this timer is exceeded
264285
VectorCopy(target->s.angles, e->s.angles);
265286
e->s.angles[PITCH] = 0;
266287
//e->monsterinfo.cost = target->monsterinfo.cost;

src/entities/drone/drone_medic.c

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -645,14 +645,19 @@ static vec3_t mymedic_cable_offsets[] =
645645
};
646646

647647
edict_t *CreateSpiker (edict_t *ent, int skill_level);
648-
edict_t *CreateObstacle (edict_t *ent, int skill_level);
648+
edict_t* CreateObstacle(edict_t* ent, int skill_level, int talent_level);
649649
edict_t* CreateGasser(edict_t* ent, int skill_level, int talent_level);
650650

651651
void M_Reanimate (edict_t *ent, edict_t *target, int r_level, float r_modifier, qboolean printMsg)
652652
{
653653
vec3_t bmin, bmax;
654654
edict_t *e;
655655

656+
// save the original monster's level
657+
// note: this isn't strictly needed for monsters since they don't apply any bonuses--it's mainly here for player-medics
658+
if (!target->monsterinfo.resurrected_level)
659+
target->monsterinfo.resurrected_level = target->monsterinfo.level;
660+
656661
if (!strcmp(target->classname, "drone") && !(target->flags & FL_UNDEAD) && target->mtype != M_DECOY && target->mtype != M_GOLEM) // can't revive decoys, golem, and undead/skeletons
657662
{
658663
// if the summoner is a player, check for sufficient monster slots
@@ -661,7 +666,7 @@ void M_Reanimate (edict_t *ent, edict_t *target, int r_level, float r_modifier,
661666

662667
target->monsterinfo.level = r_level;
663668
M_SetBoundingBox(target->mtype, bmin, bmax);
664-
669+
665670
if (G_IsValidLocation(target, target->s.origin, bmin, bmax) && M_Initialize(ent, target, 0.0f))
666671
{
667672
//gi.dprintf("resurrect drone at %.1f\n", level.time);
@@ -672,6 +677,7 @@ void M_Reanimate (edict_t *ent, edict_t *target, int r_level, float r_modifier,
672677
target->monsterinfo.resurrected_time = level.time + 10.0;
673678
target->activator = ent; // transfer ownership!
674679
target->nextthink = level.time + FRAMETIME;//1.0; note: don't delay think--this may cause undesired behavior (monster sliding)
680+
target->monsterinfo.resurrected_timeout = target->nextthink + MEDIC_RESURRECT_TIMEOUT; // resurrected monster dies when this timer is exceeded
675681
target->monsterinfo.attack_finished = level.time + 1.0;//delay attack--alternatively we can set the think func to drone_grow
676682
gi.linkentity(target);
677683
target->monsterinfo.stand(target);
@@ -753,7 +759,7 @@ void M_Reanimate (edict_t *ent, edict_t *target, int r_level, float r_modifier,
753759
VectorCopy(start, e->s.origin);
754760
gi.linkentity(e);
755761
e->nextthink = level.time + 1.0;
756-
762+
e->monsterinfo.resurrected_timeout = e->nextthink + MEDIC_RESURRECT_TIMEOUT; // resurrected monster dies when this timer is exceeded
757763
ent->num_monsters += e->monsterinfo.control_cost;
758764
ent->num_monsters_real++;
759765
// gi.bprintf(PRINT_HIGH, "adding %p (%d)\n", e, ent->num_monsters_real);
@@ -789,6 +795,7 @@ void M_Reanimate (edict_t *ent, edict_t *target, int r_level, float r_modifier,
789795
e->monsterinfo.cost = target->monsterinfo.cost;
790796
e->health = r_modifier * e->max_health;
791797
e->monsterinfo.resurrected_time = level.time + 10.0;
798+
e->monsterinfo.resurrected_timeout = e->nextthink + MEDIC_RESURRECT_TIMEOUT; // resurrected spiker dies when this timer is exceeded
792799
e->s.frame = 4;
793800
VectorCopy(target->s.origin, e->s.origin);
794801
gi.linkentity(e);
@@ -804,7 +811,7 @@ void M_Reanimate (edict_t *ent, edict_t *target, int r_level, float r_modifier,
804811
if (ent->client && (ent->num_obstacle + 1 > OBSTACLE_MAX_COUNT))
805812
return;
806813

807-
e = CreateObstacle(ent, r_level);
814+
e = CreateObstacle(ent, r_level, vrx_get_talent_level(ent, TALENT_MAGNETISM));
808815

809816
// make sure the new entity fits
810817
if (!G_IsValidLocation(target, target->s.origin, e->mins, e->maxs))
@@ -818,6 +825,7 @@ void M_Reanimate (edict_t *ent, edict_t *target, int r_level, float r_modifier,
818825
e->s.angles[PITCH] = 0;
819826
e->monsterinfo.cost = target->monsterinfo.cost;
820827
e->monsterinfo.resurrected_time = level.time + 10.0;
828+
e->monsterinfo.resurrected_timeout = e->nextthink + MEDIC_RESURRECT_TIMEOUT; // resurrected obstacle dies when this timer is exceeded
821829
e->health = r_modifier * e->max_health;
822830
e->s.frame = 6;
823831
VectorCopy(target->s.origin, e->s.origin);
@@ -848,6 +856,7 @@ void M_Reanimate (edict_t *ent, edict_t *target, int r_level, float r_modifier,
848856
e->s.angles[PITCH] = 0;
849857
e->monsterinfo.cost = target->monsterinfo.cost;
850858
e->monsterinfo.resurrected_time = level.time + 10.0;
859+
e->monsterinfo.resurrected_timeout = e->nextthink + MEDIC_RESURRECT_TIMEOUT; // resurrected gasser dies when this timer is exceeded
851860
e->health = r_modifier * e->max_health;
852861
e->s.frame = 0;
853862
VectorCopy(target->s.origin, e->s.origin);

src/entities/drone/drone_misc.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1981,6 +1981,15 @@ qboolean M_Upkeep (edict_t *self, int delay, int upkeep_cost)
19811981

19821982
e = G_GetClient(self);
19831983

1984+
// resurrected monster has expired
1985+
if (self->monsterinfo.resurrected_level && level.time > self->monsterinfo.resurrected_timeout)
1986+
{
1987+
// kill it!
1988+
self->health = 0;
1989+
self->die(self, world, world, 0, vec3_origin);
1990+
return true; // must return true for monster to continue thinking and processing death animation
1991+
}
1992+
19841993
if (!e)
19851994
return true; // probably owned by world; doesn't pay upkeep
19861995

src/g_local.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,8 @@ typedef struct
636636
float Zchange_delay; // delay before we can adjust Z position again (to prevent bouncing)
637637
qboolean Zchanged; // has our Z position changed recently?
638638
float resurrected_time; // time when resurrection from a medic is complete
639+
int resurrected_level; // used to store the original level of the monster before resurrection bonus is applied
640+
float resurrected_timeout;// time when the resurrected monster will expire
639641
float backtrack_delay; // delay until we can backtrack to a closer waypoint (to prevent getting stuck)
640642
float path_time; // time when monster can compute a path
641643
vec3_t prevGoalPos; // last goal position, used for deciding when to re-compute paths

src/quake2/g_layout.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,13 @@ sidebar_entry_t layout_add_entity_info(sidebar_t* sidebar, edict_t* ent)
315315
case M_SKELETON:
316316
case M_GOLEM:
317317
name = lva("%s", V_GetMonsterName(ent));
318-
data = lva("+%d/%d", ent->health, ent->monsterinfo.power_armor_power);
318+
if (ent->monsterinfo.resurrected_timeout)
319+
{
320+
float time_remaining = ent->monsterinfo.resurrected_timeout - level.time;
321+
data = lva("(%d) +%d/%d %.0f", ent->monsterinfo.level, ent->health, ent->monsterinfo.power_armor_power, time_remaining);
322+
}
323+
else
324+
data = lva("(%d) +%d/%d", ent->monsterinfo.level, ent->health, ent->monsterinfo.power_armor_power);
319325
break;
320326
case M_SENTRY:
321327
name = lva("sentry");
@@ -348,7 +354,13 @@ sidebar_entry_t layout_add_entity_info(sidebar_t* sidebar, edict_t* ent)
348354
case M_GASSER:
349355
case M_DECOY:
350356
name = lva("%s", V_GetMonsterName(ent));
351-
data = lva("+%d", ent->health);
357+
if (ent->monsterinfo.resurrected_timeout)
358+
{
359+
float time_remaining = ent->monsterinfo.resurrected_timeout - level.time;
360+
data = lva("(%d) +%d %.0f", ent->monsterinfo.level, ent->health, time_remaining);
361+
}
362+
else
363+
data = lva("(%d) +%d", ent->monsterinfo.level, ent->health);
352364
break;
353365
case M_BARREL:
354366
name = lva("barrel");

0 commit comments

Comments
 (0)