1 /*TODO list (things left to do before this weapon should be ready, delete once it's all done):
2 - The mine model needs to face properly when it sticks to a surface. Once we'll have a correct mine model, we can't afford the model facing any way it falls to the ground. Should probably look at the porto code to see how portals face in the right direction when sticking to walls.
6 REGISTER_WEAPON(MINE_LAYER, w_minelayer, IT_ROCKETS, 4, WEP_FLAG_NORMAL | WEP_TYPE_SPLASH, BOT_PICKUP_RATING_HIGH, "minelayer", "minelayer", "Mine Layer");
9 .float minelayer_detonate;
10 .float mine_number, mine_time;
12 void spawnfunc_weapon_minelayer (void)
14 weapon_defaultspawnfunc(WEP_MINE_LAYER);
17 void W_Mine_RespawnEntity ()
21 newmine.origin = '0 0 100';
22 newmine.model = "models/mine.md3";
28 void W_Mine_Explode ()
30 if(other.takedamage == DAMAGE_AIM)
31 if(other.classname == "player")
32 if(IsDifferentTeam(self.owner, other))
34 AnnounceTo(self.owner, "airshot");
36 self.event_damage = SUB_Null;
37 self.takedamage = DAMAGE_NO;
39 RadiusDamage (self, self.owner, cvar("g_balance_minelayer_damage"), cvar("g_balance_minelayer_edgedamage"), cvar("g_balance_minelayer_radius"), world, cvar("g_balance_minelayer_force"), self.projectiledeathtype, other);
41 if (self.owner.weapon == WEP_MINE_LAYER)
43 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
45 self.owner.cnt = WEP_MINE_LAYER;
46 ATTACK_FINISHED(self.owner) = time;
47 self.owner.switchweapon = w_getbestweapon(self.owner);
53 void W_Mine_DoRemoteExplode ()
55 self.event_damage = SUB_Null;
56 self.takedamage = DAMAGE_NO;
58 RadiusDamage (self, self.owner, cvar("g_balance_minelayer_remote_damage"), cvar("g_balance_minelayer_remote_edgedamage"), cvar("g_balance_minelayer_remote_radius"), world, cvar("g_balance_minelayer_remote_force"), self.projectiledeathtype | HITTYPE_BOUNCE, world);
60 if (self.owner.weapon == WEP_MINE_LAYER)
62 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
64 self.owner.cnt = WEP_MINE_LAYER;
65 ATTACK_FINISHED(self.owner) = time;
66 self.owner.switchweapon = w_getbestweapon(self.owner);
72 void W_Mine_RemoteExplode()
74 if(self.owner.deadflag == DEAD_NO)
75 if((self.spawnshieldtime >= 0)
76 ? (time >= self.spawnshieldtime) // timer
77 : (vlen(NearestPointOnBox(self.owner, self.origin) - self.origin) > cvar("g_balance_minelayer_radius")) // safety device
80 W_Mine_DoRemoteExplode();
84 void W_Mine_ProximityExplode()
86 // make sure no friend is in the mine's radius. If there is any, explosion is delayed until he's at a safe distance
87 if(cvar("g_balance_minelayer_protection"))
90 head = findradius(self.origin, cvar("g_balance_minelayer_radius"));
93 if(head == self.owner || !IsDifferentTeam(head, self.owner))
103 void W_Mine_Think (void)
107 self.nextthink = time;
111 self.projectiledeathtype |= HITTYPE_BOUNCE;
116 // set the mine for detonation when a foe gets too close
117 head = findradius(self.origin, cvar("g_balance_minelayer_detectionradius"));
120 if(head.classname == "player" && head.deadflag == DEAD_NO)
121 if(head != self.owner && IsDifferentTeam(head, self.owner)) // don't trigger for team mates
124 spamsound (self, CHAN_PROJECTILE, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM);
125 self.mine_time = time + cvar("g_balance_minelayer_time");
130 // explode if it's time to
131 if(self.mine_time && time >= self.mine_time)
132 W_Mine_ProximityExplode();
135 if (self.owner.weapon == WEP_MINE_LAYER)
136 if (self.owner.deadflag == DEAD_NO)
137 if (self.minelayer_detonate)
138 W_Mine_RemoteExplode();
140 if(self.csqcprojectile_clientanimate == 0)
141 UpdateCSQCProjectile(self);
144 void W_Mine_Touch (void)
147 if(!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))
149 spamsound (self, CHAN_PROJECTILE, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM);
150 self.movetype = MOVETYPE_NONE; // lock the mine in place
152 W_Mine_RespawnEntity();
154 else if(self.movetype != MOVETYPE_NONE) // don't unstick a locked mine when someone touches it
155 self.velocity = '0 0 0';
158 void W_Mine_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
160 if (self.health <= 0)
162 self.health = self.health - damage;
163 self.angles = vectoangles(self.velocity);
164 if (self.health <= 0)
165 W_PrepareExplosionByDamage(attacker, W_Mine_Explode);
168 void W_Mine_Attack (void)
173 // scan how many mines we placed, and return if we reached our limit
174 if(cvar("g_balance_minelayer_limit"))
176 self.mine_number = 0;
177 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
178 self.mine_number += 1;
180 if(self.mine_number >= cvar("g_balance_minelayer_limit"))
182 // the refire delay keeps this message from being spammed
183 sprint(self, strcat("You cannot place more than ^2", cvar_string("g_balance_minelayer_limit"), " ^7mines at a time\n") );
184 play2(self, "weapons/unavailable.wav");
189 if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
190 self.ammo_rockets = self.ammo_rockets - cvar("g_balance_minelayer_ammo");
192 W_SetupShot_ProjectileSize (self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", cvar("g_balance_minelayer_damage"));
193 pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
195 mine = WarpZone_RefSys_SpawnSameRefSys(self);
197 if(cvar("g_balance_minelayer_detonatedelay") >= 0)
198 mine.spawnshieldtime = time + cvar("g_balance_minelayer_detonatedelay");
200 mine.spawnshieldtime = -1;
201 mine.classname = "mine";
202 mine.bot_dodge = TRUE;
203 mine.bot_dodgerating = cvar("g_balance_minelayer_damage") * 2; // * 2 because it can detonate inflight which makes it even more dangerous
205 mine.takedamage = DAMAGE_YES;
206 mine.damageforcescale = cvar("g_balance_minelayer_damageforcescale");
207 mine.health = cvar("g_balance_minelayer_health");
208 mine.event_damage = W_Mine_Damage;
210 mine.movetype = MOVETYPE_TOSS;
211 PROJECTILE_MAKETRIGGER(mine);
212 mine.projectiledeathtype = WEP_MINE_LAYER;
213 setsize (mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot
215 setorigin (mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point
216 W_SetupProjectileVelocity(mine, cvar("g_balance_minelayer_speed"), 0);
217 mine.angles = vectoangles (mine.velocity);
219 mine.touch = W_Mine_Touch;
220 mine.think = W_Mine_Think;
221 mine.nextthink = time;
222 mine.cnt = time + cvar("g_balance_minelayer_lifetime");
223 mine.flags = FL_PROJECTILE;
225 CSQCProjectile(mine, FALSE, PROJECTILE_MINE, TRUE);
227 // muzzle flash for 1st person view
229 setmodel (flash, "models/flash.md3"); // precision set below
230 SUB_SetFade (flash, time, 0.1);
231 flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
232 W_AttachToShotorg(flash, '5 0 0');
237 void spawnfunc_weapon_minelayer (void); // defined in t_items.qc
239 float w_minelayer(float req)
245 // aim and decide to fire if appropriate
246 self.BUTTON_ATCK = bot_aim(cvar("g_balance_minelayer_speed"), 0, cvar("g_balance_minelayer_lifetime"), FALSE);
247 if(skill >= 2) // skill 0 and 1 bots won't detonate mines!
249 // decide whether to detonate mines
250 local entity mine, targetlist, targ;
251 local float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
252 local float selfdamage, teamdamage, enemydamage;
253 edgedamage = cvar("g_balance_minelayer_edgedamage");
254 coredamage = cvar("g_balance_minelayer_damage");
255 edgeradius = cvar("g_balance_minelayer_radius");
256 recipricoledgeradius = 1 / edgeradius;
260 targetlist = findchainfloat(bot_attack, TRUE);
261 mine = find(world, classname, "mine");
264 if (mine.owner != self)
266 mine = find(mine, classname, "mine");
272 d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin);
273 d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
274 // count potential damage according to type of target
276 selfdamage = selfdamage + d;
277 else if (targ.team == self.team && teams_matter)
278 teamdamage = teamdamage + d;
279 else if (bot_shouldattack(targ))
280 enemydamage = enemydamage + d;
283 mine = find(mine, classname, "mine");
285 local float desirabledamage;
286 desirabledamage = enemydamage;
287 if (teamplay != 1 && time > self.invincible_finished && time > self.spawnshieldtime)
288 desirabledamage = desirabledamage - selfdamage * cvar("g_balance_selfdamagepercent");
289 if (self.team && teamplay != 1)
290 desirabledamage = desirabledamage - teamdamage;
292 mine = find(world, classname, "mine");
295 if (mine.owner != self)
297 mine = find(mine, classname, "mine");
300 makevectors(mine.v_angle);
302 if (skill > 9) // normal players only do this for the target they are tracking
308 (v_forward * normalize(mine.origin - targ.origin)< 0.1)
309 && desirabledamage > 0.1*coredamage
310 )self.BUTTON_ATCK2 = TRUE;
314 local float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
315 //As the distance gets larger, a correct detonation gets near imposible
316 //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player
317 if(v_forward * normalize(mine.origin - self.enemy.origin)< 0.1)
318 if(self.enemy.classname == "player")
319 if(desirabledamage >= 0.1*coredamage)
320 if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1))
321 self.BUTTON_ATCK2 = TRUE;
322 // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
325 mine = find(mine, classname, "mine");
327 // if we would be doing at X percent of the core damage, detonate it
328 // but don't fire a new shot at the same time!
329 if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
330 self.BUTTON_ATCK2 = TRUE;
331 if ((skill > 6.5) && (selfdamage > self.health))
332 self.BUTTON_ATCK2 = FALSE;
333 //if(self.BUTTON_ATCK2 == TRUE)
334 // dprint(ftos(desirabledamage),"\n");
335 if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE;
338 else if (req == WR_THINK)
340 if (self.BUTTON_ATCK)
342 if(weapon_prepareattack(0, cvar("g_balance_minelayer_refire")))
345 weapon_thinkf(WFRAME_FIRE1, cvar("g_balance_minelayer_animtime"), w_ready);
349 if (self.BUTTON_ATCK2)
352 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
354 if(!mine.minelayer_detonate)
356 mine.minelayer_detonate = TRUE;
361 sound (self, CHAN_WEAPON2, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM);
364 else if (req == WR_PRECACHE)
366 precache_model ("models/flash.md3");
367 precache_model ("models/weapons/g_minelayer.md3");
368 precache_model ("models/weapons/v_minelayer.md3");
369 precache_model ("models/weapons/h_minelayer.iqm");
370 precache_sound ("weapons/mine_det.wav");
371 precache_sound ("weapons/mine_fire.wav");
372 precache_sound ("weapons/mine_stick.wav");
373 precache_sound ("weapons/mine_trigger.wav");
375 else if (req == WR_SETUP)
377 weapon_setup(WEP_MINE_LAYER);
379 else if (req == WR_CHECKAMMO1)
381 // don't switch while placing a mine
382 if ((ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER)
383 && self.ammo_rockets < cvar("g_balance_minelayer_ammo"))
386 else if (req == WR_CHECKAMMO2)
392 float w_minelayer(float req)
394 if(req == WR_IMPACTEFFECT)
397 org2 = w_org + w_backoff * 12;
398 pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
400 sound(self, CHAN_PROJECTILE, "weapons/mine_exp.wav", VOL_BASE, ATTN_NORM);
402 else if(req == WR_PRECACHE)
404 precache_sound("weapons/mine_exp.wav");
406 else if (req == WR_SUICIDEMESSAGE)
407 w_deathtypestring = "%s exploded";
408 else if (req == WR_KILLMESSAGE)
410 if(w_deathtype & HITTYPE_BOUNCE) // (remote detonation)
411 w_deathtypestring = "%s got too close to %s's mine";
412 else if(w_deathtype & HITTYPE_SPLASH)
413 w_deathtypestring = "%s almost dodged %s's mine";
415 w_deathtypestring = "%s stepped on %s's mine";