1 /*TODO list (things left to do before this weapon should be ready, delete once it's all done):
2 - The weapon currently uses sounds and models from other weapons. We need a modeler and sound artist to make this weapon its own (the gun model should probably be something between the porto and rocket launcher design-wise).
3 - 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.
7 REGISTER_WEAPON(MINE_LAYER, w_minelayer, IT_ROCKETS, 4, WEP_FLAG_NORMAL | WEP_TYPE_SPLASH, BOT_PICKUP_RATING_HIGH, "minelayer", "minelayer", "Mine Layer");
10 .float minelayer_detonate;
11 .float mine_number, mine_time;
13 void spawnfunc_weapon_minelayer (void)
15 weapon_defaultspawnfunc(WEP_MINE_LAYER);
18 void W_Mine_Explode ()
20 if(other.takedamage == DAMAGE_AIM)
21 if(other.classname == "player")
22 if(IsDifferentTeam(self.owner, other))
24 AnnounceTo(self.owner, "airshot");
26 self.event_damage = SUB_Null;
27 self.takedamage = DAMAGE_NO;
29 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);
31 if (self.owner.weapon == WEP_MINE_LAYER)
33 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
35 self.owner.cnt = WEP_MINE_LAYER;
36 ATTACK_FINISHED(self.owner) = time;
37 self.owner.switchweapon = w_getbestweapon(self.owner);
43 void W_Mine_DoRemoteExplode ()
45 self.event_damage = SUB_Null;
46 self.takedamage = DAMAGE_NO;
48 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);
50 if (self.owner.weapon == WEP_MINE_LAYER)
52 if(self.owner.ammo_rockets < cvar("g_balance_minelayer_ammo"))
54 self.owner.cnt = WEP_MINE_LAYER;
55 ATTACK_FINISHED(self.owner) = time;
56 self.owner.switchweapon = w_getbestweapon(self.owner);
62 void W_Mine_RemoteExplode()
64 if(self.owner.deadflag == DEAD_NO)
65 if((self.spawnshieldtime >= 0)
66 ? (time >= self.spawnshieldtime) // timer
67 : (vlen(NearestPointOnBox(self.owner, self.origin) - self.origin) > cvar("g_balance_minelayer_radius")) // safety device
70 W_Mine_DoRemoteExplode();
74 void W_Mine_ProximityExplode()
76 // make sure no friend is in the mine's radius. If there is any, explosion is delayed until he's at a safe distance
77 if(cvar("g_balance_minelayer_protection"))
80 head = findradius(self.origin, cvar("g_balance_minelayer_radius"));
83 if(head == self.owner || !IsDifferentTeam(head, self.owner))
93 void W_Mine_Think (void)
97 self.nextthink = time;
101 self.projectiledeathtype |= HITTYPE_BOUNCE;
106 // set the mine for detonation when a foe gets too close
107 head = findradius(self.origin, cvar("g_balance_minelayer_detectionradius"));
110 if(head.classname == "player" && head.deadflag == DEAD_NO)
111 if(head != self.owner && IsDifferentTeam(head, self.owner)) // don't trigger for team mates
114 spamsound (self, CHAN_PROJECTILE, "weapons/mine_trigger.wav", VOL_BASE, ATTN_NORM);
115 self.mine_time = time + cvar("g_balance_minelayer_time");
120 // explode if it's time to
121 if(self.mine_time && time >= self.mine_time)
122 W_Mine_ProximityExplode();
125 if (self.owner.weapon == WEP_MINE_LAYER)
126 if (self.owner.deadflag == DEAD_NO)
127 if (self.minelayer_detonate)
128 W_Mine_RemoteExplode();
130 if(self.csqcprojectile_clientanimate == 0)
131 UpdateCSQCProjectile(self);
134 void W_Mine_Touch (void)
137 if(!other || (other.takedamage != DAMAGE_AIM && other.movetype == MOVETYPE_NONE))
139 spamsound (self, CHAN_PROJECTILE, "weapons/mine_stick.wav", VOL_BASE, ATTN_NORM);
140 self.movetype = MOVETYPE_NONE; // lock the mine in place
142 else if(self.movetype != MOVETYPE_NONE) // don't unstick a locked mine when someone touches it
143 self.velocity = '0 0 0';
146 void W_Mine_Damage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
148 if (self.health <= 0)
150 self.health = self.health - damage;
151 self.angles = vectoangles(self.velocity);
152 if (self.health <= 0)
153 W_PrepareExplosionByDamage(attacker, W_Mine_Explode);
156 void W_Mine_Attack (void)
161 // scan how many mines we placed, and return if we reached our limit
162 if(cvar("g_balance_minelayer_limit"))
164 self.mine_number = 0;
165 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
166 self.mine_number += 1;
168 if(self.mine_number >= cvar("g_balance_minelayer_limit"))
170 // the refire delay keeps this message from being spammed
171 sprint(self, strcat("You cannot place more than ^2", cvar_string("g_balance_minelayer_limit"), " ^7mines at a time\n") );
172 play2(self, "weapons/unavailable.wav");
177 if not(self.items & IT_UNLIMITED_WEAPON_AMMO)
178 self.ammo_rockets = self.ammo_rockets - cvar("g_balance_minelayer_ammo");
180 W_SetupShot_ProjectileSize (self, '-4 -4 -4', '4 4 4', FALSE, 5, "weapons/mine_fire.wav", cvar("g_balance_minelayer_damage"));
181 pointparticles(particleeffectnum("rocketlauncher_muzzleflash"), w_shotorg, w_shotdir * 1000, 1);
183 mine = WarpZone_RefSys_SpawnSameRefSys(self);
185 if(cvar("g_balance_minelayer_detonatedelay") >= 0)
186 mine.spawnshieldtime = time + cvar("g_balance_minelayer_detonatedelay");
188 mine.spawnshieldtime = -1;
189 mine.classname = "mine";
190 mine.bot_dodge = TRUE;
191 mine.bot_dodgerating = cvar("g_balance_minelayer_damage") * 2; // * 2 because it can detonate inflight which makes it even more dangerous
193 mine.takedamage = DAMAGE_YES;
194 mine.damageforcescale = cvar("g_balance_minelayer_damageforcescale");
195 mine.health = cvar("g_balance_minelayer_health");
196 mine.event_damage = W_Mine_Damage;
198 mine.movetype = MOVETYPE_TOSS;
199 PROJECTILE_MAKETRIGGER(mine);
200 mine.projectiledeathtype = WEP_MINE_LAYER;
201 setsize (mine, '-4 -4 -4', '4 4 4'); // give it some size so it can be shot
203 setorigin (mine, w_shotorg - v_forward * 4); // move it back so it hits the wall at the right point
204 W_SetupProjectileVelocity(mine, cvar("g_balance_minelayer_speed"), 0);
205 mine.angles = vectoangles (mine.velocity);
207 mine.touch = W_Mine_Touch;
208 mine.think = W_Mine_Think;
209 mine.nextthink = time;
210 mine.cnt = time + cvar("g_balance_minelayer_lifetime");
211 mine.flags = FL_PROJECTILE;
213 CSQCProjectile(mine, FALSE, PROJECTILE_MINE, TRUE);
215 // muzzle flash for 1st person view
217 setmodel (flash, "models/flash.md3"); // precision set below
218 SUB_SetFade (flash, time, 0.1);
219 flash.effects = EF_ADDITIVE | EF_FULLBRIGHT | EF_LOWPRECISION;
220 W_AttachToShotorg(flash, '5 0 0');
225 void spawnfunc_weapon_minelayer (void); // defined in t_items.qc
227 float w_minelayer(float req)
233 // aim and decide to fire if appropriate
234 self.BUTTON_ATCK = bot_aim(cvar("g_balance_minelayer_speed"), 0, cvar("g_balance_minelayer_lifetime"), FALSE);
235 if(skill >= 2) // skill 0 and 1 bots won't detonate mines!
237 // decide whether to detonate mines
238 local entity mine, targetlist, targ;
239 local float edgedamage, coredamage, edgeradius, recipricoledgeradius, d;
240 local float selfdamage, teamdamage, enemydamage;
241 edgedamage = cvar("g_balance_minelayer_edgedamage");
242 coredamage = cvar("g_balance_minelayer_damage");
243 edgeradius = cvar("g_balance_minelayer_radius");
244 recipricoledgeradius = 1 / edgeradius;
248 targetlist = findchainfloat(bot_attack, TRUE);
249 mine = find(world, classname, "mine");
252 if (mine.owner != self)
254 mine = find(mine, classname, "mine");
260 d = vlen(targ.origin + (targ.mins + targ.maxs) * 0.5 - mine.origin);
261 d = bound(0, edgedamage + (coredamage - edgedamage) * sqrt(1 - d * recipricoledgeradius), 10000);
262 // count potential damage according to type of target
264 selfdamage = selfdamage + d;
265 else if (targ.team == self.team && teams_matter)
266 teamdamage = teamdamage + d;
267 else if (bot_shouldattack(targ))
268 enemydamage = enemydamage + d;
271 mine = find(mine, classname, "mine");
273 local float desirabledamage;
274 desirabledamage = enemydamage;
275 if (teamplay != 1 && time > self.invincible_finished && time > self.spawnshieldtime)
276 desirabledamage = desirabledamage - selfdamage * cvar("g_balance_selfdamagepercent");
277 if (self.team && teamplay != 1)
278 desirabledamage = desirabledamage - teamdamage;
280 mine = find(world, classname, "mine");
283 if (mine.owner != self)
285 mine = find(mine, classname, "mine");
288 makevectors(mine.v_angle);
290 if (skill > 9) // normal players only do this for the target they are tracking
296 (v_forward * normalize(mine.origin - targ.origin)< 0.1)
297 && desirabledamage > 0.1*coredamage
298 )self.BUTTON_ATCK2 = TRUE;
302 local float distance; distance= bound(300,vlen(self.origin-self.enemy.origin),30000);
303 //As the distance gets larger, a correct detonation gets near imposible
304 //Bots are assumed to use the mine spawnfunc_light to see if the mine gets near a player
305 if(v_forward * normalize(mine.origin - self.enemy.origin)< 0.1)
306 if(self.enemy.classname == "player")
307 if(desirabledamage >= 0.1*coredamage)
308 if(random()/distance*300 > frametime*bound(0,(10-skill)*0.2,1))
309 self.BUTTON_ATCK2 = TRUE;
310 // dprint(ftos(random()/distance*300),">");dprint(ftos(frametime*bound(0,(10-skill)*0.2,1)),"\n");
313 mine = find(mine, classname, "mine");
315 // if we would be doing at X percent of the core damage, detonate it
316 // but don't fire a new shot at the same time!
317 if (desirabledamage >= 0.75 * coredamage) //this should do group damage in rare fortunate events
318 self.BUTTON_ATCK2 = TRUE;
319 if ((skill > 6.5) && (selfdamage > self.health))
320 self.BUTTON_ATCK2 = FALSE;
321 //if(self.BUTTON_ATCK2 == TRUE)
322 // dprint(ftos(desirabledamage),"\n");
323 if (self.BUTTON_ATCK2 == TRUE) self.BUTTON_ATCK = FALSE;
326 else if (req == WR_THINK)
328 if (self.BUTTON_ATCK)
330 if(weapon_prepareattack(0, cvar("g_balance_minelayer_refire")))
333 weapon_thinkf(WFRAME_FIRE1, cvar("g_balance_minelayer_animtime"), w_ready);
337 if (self.BUTTON_ATCK2)
340 for(mine = world; (mine = find(mine, classname, "mine")); ) if(mine.owner == self)
342 if(!mine.minelayer_detonate)
344 mine.minelayer_detonate = TRUE;
349 sound (self, CHAN_WEAPON2, "weapons/mine_det.wav", VOL_BASE, ATTN_NORM);
352 else if (req == WR_PRECACHE)
354 precache_model ("models/flash.md3");
355 precache_model ("models/weapons/g_minelayer.md3");
356 precache_model ("models/weapons/v_minelayer.md3");
357 precache_model ("models/weapons/h_minelayer.iqm");
358 precache_sound ("weapons/mine_det.wav");
359 precache_sound ("weapons/mine_fire.wav");
360 precache_sound ("weapons/mine_stick.wav");
361 precache_sound ("weapons/mine_trigger.wav");
363 else if (req == WR_SETUP)
365 weapon_setup(WEP_MINE_LAYER);
367 else if (req == WR_CHECKAMMO1)
369 // don't switch while placing a mine
370 if ((ATTACK_FINISHED(self) <= time || self.weapon != WEP_MINE_LAYER)
371 && self.ammo_rockets < cvar("g_balance_minelayer_ammo"))
374 else if (req == WR_CHECKAMMO2)
380 float w_minelayer(float req)
382 if(req == WR_IMPACTEFFECT)
385 org2 = w_org + w_backoff * 12;
386 pointparticles(particleeffectnum("rocket_explode"), org2, '0 0 0', 1);
388 sound(self, CHAN_PROJECTILE, "weapons/mine_exp.wav", VOL_BASE, ATTN_NORM);
390 else if(req == WR_PRECACHE)
392 precache_sound("weapons/mine_exp.wav");
394 else if (req == WR_SUICIDEMESSAGE)
395 w_deathtypestring = "%s exploded";
396 else if (req == WR_KILLMESSAGE)
398 if(w_deathtype & HITTYPE_BOUNCE) // (remote detonation)
399 w_deathtypestring = "%s got too close to %s's mine";
400 else if(w_deathtype & HITTYPE_SPLASH)
401 w_deathtypestring = "%s almost dodged %s's mine";
403 w_deathtypestring = "%s stepped on %s's mine";