Skip to content

Commit 58e95a5

Browse files
committed
Removed mage Improved Magicbolt talent. Added mage Wizardry talent, which replaces spell's ability delay with a spell-specific timer, allowing multiple spells to be used simultaneously. Added mage CL Storm talent, which adds a chance for lightning storm to fire a chain lightning bolt. Finally, reworked chain lightning to use TE_HEATBEAM graphics.
1 parent d44ef39 commit 58e95a5

16 files changed

Lines changed: 359 additions & 55 deletions

File tree

src/characters/Talents.c

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ const talentdef_t talents_necromancer[] = {
6666
const talentdef_t talents_mage[] = {
6767
// {TALENT_ICE_BOLT, 5, false},
6868
// {TALENT_FROST_NOVA, 5, false},
69-
{TALENT_IMP_MAGICBOLT, 5, false},
69+
// {TALENT_IMP_MAGICBOLT, 5, false},
70+
{TALENT_CL_STORM, 5, false},
71+
{TALENT_WIZARDRY, 5, false},
7072
{TALENT_MANASHIELD, 5, false},
7173
{TALENT_MEDITATION, 5, false},
7274
{TALENT_OVERLOAD, 5, false},
@@ -445,11 +447,22 @@ int writeTalentDescription(edict_t *ent, int talentID) {
445447
menu_add_line(ent, "Special nova spell", MENU_WHITE_CENTERED);
446448
menu_add_line(ent, "that chills players.", MENU_WHITE_CENTERED);
447449
menu_add_line(ent, "(cmd frostnova)", MENU_WHITE_CENTERED);
448-
return 3;*/
450+
return 3;
449451
case TALENT_IMP_MAGICBOLT:
450452
menu_add_line(ent, "Power cubes are refunded", MENU_WHITE_CENTERED);
451453
menu_add_line(ent, "on successful hits.", MENU_WHITE_CENTERED);
452-
return 2;
454+
return 2;*/
455+
case TALENT_WIZARDRY:
456+
menu_add_line(ent, "Switches spell timers to be", MENU_WHITE_CENTERED);
457+
menu_add_line(ent, "ability-specific instead of", MENU_WHITE_CENTERED);
458+
menu_add_line(ent, "global, allowing you to use", MENU_WHITE_CENTERED);
459+
menu_add_line(ent, "them simultaneously!", MENU_WHITE_CENTERED);
460+
return 4;
461+
case TALENT_CL_STORM:
462+
menu_add_line(ent, "Adds chance for lightning", MENU_WHITE_CENTERED);
463+
menu_add_line(ent, "storms to fire chain", MENU_WHITE_CENTERED);
464+
menu_add_line(ent, "lightning!", MENU_WHITE_CENTERED);
465+
return 3;
453466
case TALENT_MANASHIELD:
454467
menu_add_line(ent, "Reduces physical damage", MENU_WHITE_CENTERED);
455468
menu_add_line(ent, "by 80%%. All damage", MENU_WHITE_CENTERED);

src/characters/Talents.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030
#define TALENT_ICE_BOLT 40
3131
#define TALENT_MEDITATION 41
3232
#define TALENT_OVERLOAD 42
33-
#define TALENT_FROST_NOVA 43
34-
#define TALENT_IMP_MAGICBOLT 44
33+
#define TALENT_CL_STORM 43 //TALENT_FROST_NOVA
34+
#define TALENT_WIZARDRY 44 //TALENT_IMP_MAGICBOLT
3535
#define TALENT_MANASHIELD 45
3636
//Engineer
3737
//#define TALENT_DEFENSIVE_CRATE 350

src/characters/v_utils.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,12 @@ char *GetTalentString(int talent_ID) {
424424
return "Mana Charge";
425425
//case TALENT_FROST_NOVA:
426426
// return "Frost Nova";
427-
case TALENT_IMP_MAGICBOLT:
428-
return "Imp. Magicbolt";
427+
//case TALENT_IMP_MAGICBOLT:
428+
// return "Imp. Magicbolt";
429+
case TALENT_CL_STORM:
430+
return "CL Storm";
431+
case TALENT_WIZARDRY:
432+
return "Wizardry";
429433
case TALENT_MANASHIELD:
430434
return "Mana Shield";
431435
case TALENT_OVERLOAD:

src/combat/abilities/chainlightning.c

Lines changed: 177 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#define CLIGHTNING_MAX_HOPS 4
44

5+
void lightningstorm_sound(edict_t* self);
6+
57
void G_SpawnParticleTrail (vec3_t start, vec3_t end, int particles, int color)
68
{
79
int i, spacing;
@@ -64,6 +66,170 @@ qboolean ChainLightning_Attack (edict_t *ent, edict_t *target, int damage, int m
6466
return result;
6567
}
6668

69+
void CL_attack(edict_t* self, edict_t *target, int damage)
70+
{
71+
//if (G_ValidTarget(self, target, true, false))
72+
//{
73+
// do less damage to corpses
74+
if (target->health < 1 && damage > 100)
75+
damage = 100;
76+
//gi.dprintf("CL did %d damage at %d\n", damage, (int)level.framenum);
77+
// deal damage
78+
T_Damage(target, self, self, vec3_origin, target->s.origin, vec3_origin, damage, 0, DAMAGE_ENERGY, MOD_LIGHTNING);
79+
//return true;
80+
//}
81+
//return false;
82+
}
83+
84+
qboolean CL_targetinlist(edict_t* self, edict_t *target)
85+
{
86+
for (int i = 0; i < self->monsterinfo.target_index; i++)
87+
{
88+
if (target == self->monsterinfo.dmglist[i].player)
89+
return true;
90+
}
91+
return false;
92+
}
93+
94+
edict_t *CL_findtarget(edict_t* self, vec3_t org, float radius, qboolean check_list)
95+
{
96+
edict_t* e = NULL;
97+
98+
while ((e = findclosestradius(e, org, radius)) != NULL)
99+
{
100+
if (!G_ValidTarget(self, e, true, false))
101+
continue;
102+
if (check_list && CL_targetinlist(self, e))
103+
continue;
104+
return e;
105+
}
106+
return NULL;
107+
}
108+
109+
void chainlightning_think(edict_t* self)
110+
{
111+
edict_t *enemy = CL_findtarget(self, self->s.origin, self->monsterinfo.sight_range, true);
112+
113+
if (self->light_level && enemy) // have ammo and valid target
114+
{
115+
vec3_t start;
116+
117+
//gi.dprintf("%d: CL is attacking, ammo: %d\n", (int)level.framenum, self->light_level);
118+
// add enemy to the list so we don't attack the same target twice
119+
self->monsterinfo.dmglist[self->monsterinfo.target_index++].player = enemy;
120+
121+
CL_attack(self, enemy, self->dmg);
122+
// move
123+
G_EntMidPoint(enemy, start);
124+
VectorCopy(start, self->s.origin);
125+
gi.linkentity(self);
126+
127+
// lightning graphical effect
128+
gi.WriteByte(svc_temp_entity);
129+
gi.WriteByte(TE_HEATBEAM);
130+
gi.WriteShort(self - g_edicts);
131+
gi.WritePosition(self->s.old_origin);
132+
gi.WritePosition(self->s.origin);
133+
gi.multicast(self->s.origin, MULTICAST_PVS);
134+
135+
VectorCopy(start, self->s.old_origin);
136+
137+
// reduce ammo
138+
self->light_level--;
139+
// increase damage for next hop
140+
self->dmg *= CLIGHTNING_DMG_MOD;
141+
}
142+
else
143+
{
144+
//gi.dprintf("%d: CL is being removed, ammo: %d\n", (int)level.framenum, self->light_level);
145+
146+
G_FreeEdict(self);
147+
return;
148+
}
149+
150+
self->nextthink = level.time + FRAMETIME;
151+
}
152+
153+
void fire_chainlightning(edict_t* self, vec3_t start, vec3_t aimdir, int damage, float radius, int attack_range, int hop_range, int max_hops)
154+
{
155+
vec3_t end;
156+
edict_t* enemy = NULL;
157+
158+
// calling entity made a sound, used to alert monsters
159+
self->lastsound = level.framenum;
160+
// play sound
161+
gi.sound(self, CHAN_ITEM, gi.soundindex("abilities/thunderbolt.wav"), 1, ATTN_NORM, 0);
162+
//lightningstorm_sound(self);
163+
164+
// get ending position
165+
VectorMA(start, attack_range, aimdir, end);
166+
167+
// trace from attacker to ending position
168+
trace_t tr = gi.trace(start, NULL, NULL, end, self, MASK_SHOT);
169+
170+
//vec3_t org;
171+
// try to attack
172+
if (radius > 0)
173+
{
174+
edict_t* targ;
175+
if ((targ = CL_findtarget(self, tr.endpos, radius, false)) != NULL)
176+
{
177+
//FIXME: randomize endpos within the bbox of target
178+
CL_attack(self, targ, damage);
179+
enemy = targ;
180+
G_EntMidPoint(targ, end);
181+
end[0] = end[0] + GetRandom(0, (int)targ->maxs[0]) * crandom();
182+
end[1] = end[1] + GetRandom(0, (int)targ->maxs[1]) * crandom();
183+
// recalculate starting position directly above endpoint
184+
trace_t tr = gi.trace(end, NULL, NULL, tv(end[0], end[1], 8192), self, MASK_SOLID);
185+
VectorCopy(tr.endpos, start);
186+
}
187+
else
188+
VectorCopy(tr.endpos, end);
189+
}
190+
else if (G_ValidTarget(self, tr.ent, true, false))
191+
{
192+
CL_attack(self, tr.ent, damage);
193+
enemy = tr.ent;
194+
VectorCopy(tr.endpos, end);
195+
}
196+
//else
197+
// return;
198+
199+
// lightning graphical effect
200+
gi.WriteByte(svc_temp_entity);
201+
gi.WriteByte(TE_HEATBEAM);
202+
gi.WriteShort(self - g_edicts);
203+
gi.WritePosition(start);
204+
gi.WritePosition(end);
205+
gi.multicast(end, MULTICAST_PVS);
206+
207+
if (!enemy)
208+
return;
209+
210+
//gi.dprintf("%d: spawned CL ent\n", (int)level.framenum);
211+
// spawn lightning entity
212+
edict_t* lightning = G_Spawn();
213+
lightning->classname = "lightning";
214+
lightning->solid = SOLID_NOT;
215+
lightning->svflags |= SVF_NOCLIENT;
216+
lightning->owner = G_GetClient(self); // for lightning storm: to ensure the owner is the player, not the LS!
217+
//lightning->delay = level.time + duration;
218+
lightning->nextthink = level.time + FRAMETIME;
219+
lightning->think = chainlightning_think;
220+
VectorCopy(end, lightning->s.origin);
221+
VectorCopy(end, lightning->s.old_origin);
222+
VectorCopy(aimdir, lightning->movedir);
223+
lightning->dmg_radius = radius;
224+
lightning->dmg = damage * CLIGHTNING_DMG_MOD; // damage increases with each hop
225+
lightning->monsterinfo.sight_range = hop_range;
226+
lightning->light_level = max_hops;
227+
gi.linkentity(lightning);
228+
229+
// add enemy to the list so we don't attack again
230+
lightning->monsterinfo.dmglist[lightning->monsterinfo.target_index++].player = enemy;
231+
}
232+
67233
void ChainLightning (edict_t *ent, vec3_t start, vec3_t aimdir, int damage, int attack_range, int hop_range)
68234
{
69235
int i=0, hops=CLIGHTNING_MAX_HOPS;
@@ -201,9 +367,18 @@ void Cmd_ChainLightning_f (edict_t *ent, float skill_mult, float cost_mult)
201367
VectorSet(offset, 0, 8, ent->viewheight-8);
202368
P_ProjectSource(ent->client, ent->s.origin, offset, forward, right, start);
203369

204-
ChainLightning(ent, start, forward, damage, attack_range, hop_range);
370+
//ChainLightning(ent, start, forward, damage, attack_range, hop_range);
371+
fire_chainlightning(ent, start, forward, damage, 0, attack_range, hop_range, 4);
205372

206-
ent->client->ability_delay = level.time + CLIGHTNING_DELAY/* * cost_mult*/;
373+
//Talent: Wizardry - makes spell timer ability-specific instead of global
374+
int talentLevel = vrx_get_talent_level(ent, TALENT_WIZARDRY);
375+
if (talentLevel > 0)
376+
{
377+
ent->myskills.abilities[LIGHTNING].delay = level.time + CLIGHTNING_DELAY;
378+
ent->client->ability_delay = level.time + CLIGHTNING_DELAY * (1 - 0.2 * talentLevel);
379+
}
380+
else
381+
ent->client->ability_delay = level.time + CLIGHTNING_DELAY/* * cost_mult*/;
207382
ent->client->pers.inventory[power_cube_index] -= cost;
208383
}
209384

src/combat/abilities/fire.c

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,16 @@ void Cmd_Fireball_f(edict_t* ent, float skill_mult, float cost_mult)
284284

285285
fire_fireball(ent, start, forward, damage, radius, speed, flames, flamedmg);
286286

287-
ent->client->ability_delay = level.time + FIREBALL_DELAY/* * cost_mult*/;
287+
//Talent: Wizardry - makes spell timer ability-specific instead of global
288+
int talentLevel = vrx_get_talent_level(ent, TALENT_WIZARDRY);
289+
if (talentLevel > 0)
290+
{
291+
ent->myskills.abilities[FIREBALL].delay = level.time + FIREBALL_DELAY;
292+
ent->client->ability_delay = level.time + FIREBALL_DELAY * (1 - 0.2 * talentLevel);
293+
}
294+
else
295+
ent->client->ability_delay = level.time + FIREBALL_DELAY/* * cost_mult*/;
296+
288297
ent->client->pers.inventory[power_cube_index] -= cost;
289298

290299
// write a nice effect so everyone knows we've cast a spell
@@ -542,7 +551,15 @@ void Cmd_Firewall_f(edict_t* ent, float skill_mult, float cost_mult)
542551
// entity made a sound, used to alert monsters
543552
ent->lastsound = level.framenum;
544553

545-
ent->client->ability_delay = level.time + FIREWALL_DELAY;
554+
//Talent: Wizardry - makes spell timer ability-specific instead of global
555+
int talentLevel = vrx_get_talent_level(ent, TALENT_WIZARDRY);
556+
if (talentLevel > 0)
557+
{
558+
ent->myskills.abilities[FIREWALL].delay = level.time + FIREWALL_DELAY;
559+
ent->client->ability_delay = level.time + FIREWALL_DELAY * (1 - 0.2 * talentLevel);
560+
}
561+
else
562+
ent->client->ability_delay = level.time + FIREWALL_DELAY;
546563
ent->client->pers.inventory[power_cube_index] -= cost;
547564
ent->num_firewalls++;// increase firewall counter
548565
}

src/combat/abilities/flying_skull.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ void skull_movetogoal (edict_t *self, edict_t *goal)
381381
gi.linkentity(self);
382382
}
383383

384-
void ChainLightning(edict_t* ent, vec3_t start, vec3_t aimdir, int damage, int attack_range, int hop_range);
384+
//void ChainLightning(edict_t* ent, vec3_t start, vec3_t aimdir, int damage, int attack_range, int hop_range);
385385

386386
void skull_attack (edict_t *self)
387387
{
@@ -447,7 +447,8 @@ void skull_attack (edict_t *self)
447447
{
448448
int cl_dmg = 10 * damage;
449449
//gi.dprintf("hellspawn firing CL. base dmg %d modified %d CL %d\n", self->dmg, damage, cl_dmg);
450-
ChainLightning(self, start, v, cl_dmg, SKULL_ATTACK_RANGE, CLIGHTNING_INITIAL_HR);
450+
//ChainLightning(self, start, v, cl_dmg, SKULL_ATTACK_RANGE, CLIGHTNING_INITIAL_HR);
451+
fire_chainlightning(self, start, v, cl_dmg, 0, SKULL_ATTACK_RANGE, CLIGHTNING_INITIAL_HR, 4);
451452
self->autocurse_delay = level.framenum + (int)(1 / FRAMETIME);
452453
return; // done attacking
453454
}

src/combat/abilities/ice.c

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,8 @@ void Cmd_GlacialSpike_f(edict_t* ent, float skill_mult, float cost_mult)
327327
radius = GLACIAL_SPIKE_INITIAL_RADIUS + GLACIAL_SPIKE_ADDON_RADIUS * skill_level;
328328
speed = GLACIAL_SPIKE_INITIAL_SPEED + GLACIAL_SPIKE_ADDON_SPEED * skill_level;
329329

330+
331+
330332
//gi.dprintf("dmg:%d slvl:%d skill_mult:%f chill_duration:%f\n",damage,slvl,skill_mult,chill_duration);
331333

332334
// get starting position and forward vector
@@ -336,7 +338,16 @@ void Cmd_GlacialSpike_f(edict_t* ent, float skill_mult, float cost_mult)
336338

337339
fire_icebolt(ent, start, forward, damage, radius, speed, 2 * skill_level, chill_duration, freeze_duration);
338340

339-
ent->client->ability_delay = level.time + GLACIAL_SPIKE_DELAY/* * cost_mult*/;
341+
//Talent: Wizardry - makes spell timer ability-specific instead of global
342+
int talentLevel = vrx_get_talent_level(ent, TALENT_WIZARDRY);
343+
if (talentLevel > 0)
344+
{
345+
ent->myskills.abilities[GLACIAL_SPIKE].delay = level.time + GLACIAL_SPIKE_DELAY;
346+
ent->client->ability_delay = level.time + GLACIAL_SPIKE_DELAY * (1 - 0.2 * talentLevel);
347+
}
348+
else
349+
ent->client->ability_delay = level.time + GLACIAL_SPIKE_DELAY/* * cost_mult*/;
350+
340351
ent->client->pers.inventory[power_cube_index] -= cost;
341352

342353
// write a nice effect so everyone knows we've cast a spell
@@ -546,7 +557,16 @@ void Cmd_FrozenOrb_f(edict_t* ent, float skill_mult, float cost_mult)
546557

547558
fire_frozenorb(ent, start, forward, damage, (2 * skill_level), chill_duration);
548559

549-
ent->client->ability_delay = level.time + FROZEN_ORB_DELAY;
560+
//Talent: Wizardry - makes spell timer ability-specific instead of global
561+
int talentLevel = vrx_get_talent_level(ent, TALENT_WIZARDRY);
562+
if (talentLevel > 0)
563+
{
564+
ent->myskills.abilities[FROZEN_ORB].delay = level.time + FROZEN_ORB_DELAY;
565+
ent->client->ability_delay = level.time + FROZEN_ORB_DELAY * (1 - 0.2 * talentLevel);
566+
//gi.dprintf("%d: %s: delay: %.1f ability_delay: %.1f\n", (int)level.framenum, __func__, ent->myskills.abilities[GLACIAL_SPIKE].delay, ent->client->ability_delay);
567+
}
568+
else
569+
ent->client->ability_delay = level.time + FROZEN_ORB_DELAY;
550570
ent->client->pers.inventory[power_cube_index] -= cost;
551571

552572
// write a nice effect so everyone knows we've cast a spell

0 commit comments

Comments
 (0)