~ubuntu-branches/debian/stretch/assaultcube-data/stretch

« back to all changes in this revision

Viewing changes to source/src/weapon.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Gonéri Le Bouder, Ansgar Burchardt, Gonéri Le Bouder
  • Date: 2010-04-02 23:37:55 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20100402233755-kf74fxwlu634o6vg
Tags: 1.0.4+repack1-1
[ Ansgar Burchardt ]
* debian/control: fix typo in short description

[ Gonéri Le Bouder ]
* Upgrade to 1.0.4
* bump standards-version to 3.8.4
* Add Depends: ${misc:Depends} just to avoid a lintian warning
* Add a debian/source/format file for the same reason

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// weapon.cpp: all shooting and effects code
 
2
 
 
3
#include "pch.h"
 
4
#include "cube.h"
 
5
#include "bot/bot.h"
 
6
#include "hudgun.h"
 
7
 
 
8
VARP(autoreload, 0, 1, 1);
 
9
 
 
10
vec sg[SGRAYS];
 
11
 
 
12
void updatelastaction(playerent *d)
 
13
{
 
14
    loopi(NUMGUNS) d->weapons[i]->updatetimers();
 
15
    d->lastaction = lastmillis;
 
16
}
 
17
 
 
18
void checkweaponswitch()
 
19
{
 
20
        if(!player1->weaponchanging) return;
 
21
    int timeprogress = lastmillis-player1->weaponchanging;
 
22
    if(timeprogress>weapon::weaponchangetime)
 
23
        {
 
24
        addmsg(SV_WEAPCHANGE, "ri", player1->weaponsel->type);
 
25
                player1->weaponchanging = 0;
 
26
        }
 
27
    else if(timeprogress>weapon::weaponchangetime/2)
 
28
    {
 
29
        player1->weaponsel = player1->nextweaponsel;
 
30
    }
 
31
}
 
32
 
 
33
void selectweapon(weapon *w)
 
34
{
 
35
    if(!w || !player1->weaponsel->deselectable()) return;
 
36
    if(w->selectable())
 
37
    {
 
38
        // substitute akimbo
 
39
        weapon *akimbo = player1->weapons[GUN_AKIMBO];
 
40
        if(w->type==GUN_PISTOL && akimbo->selectable()) w = akimbo;
 
41
 
 
42
        player1->weaponswitch(w);
 
43
    }
 
44
}
 
45
 
 
46
void selectweaponi(int w)
 
47
{
 
48
    if(player1->state == CS_ALIVE && w >= 0 && w < NUMGUNS)
 
49
    {
 
50
        selectweapon(player1->weapons[w]);
 
51
    }
 
52
}
 
53
 
 
54
void shiftweapon(int s)
 
55
{
 
56
    if(player1->state == CS_ALIVE)
 
57
    {
 
58
        if(!player1->weaponsel->deselectable()) return;
 
59
 
 
60
        weapon *curweapon = player1->weaponsel;
 
61
        weapon *akimbo = player1->weapons[GUN_AKIMBO];
 
62
 
 
63
        // collect available weapons
 
64
        vector<weapon *> availweapons;
 
65
        loopi(NUMGUNS)
 
66
        {
 
67
            weapon *w = player1->weapons[i];
 
68
            if(!w) continue;
 
69
            if(w->selectable() || w==curweapon || (w->type==GUN_PISTOL && player1->akimbo))
 
70
            {
 
71
                availweapons.add(w);
 
72
            }
 
73
        }
 
74
 
 
75
        // replace pistol by akimbo
 
76
        if(player1->akimbo)
 
77
        {
 
78
            availweapons.removeobj(akimbo); // and remove initial akimbo
 
79
            int pistolidx = availweapons.find(player1->weapons[GUN_PISTOL]);
 
80
            if(pistolidx>=0) availweapons[pistolidx] = akimbo; // insert at pistols position
 
81
            if(curweapon->type==GUN_PISTOL) curweapon = akimbo; // fix selection
 
82
        }
 
83
 
 
84
        // detect the next weapon
 
85
        int num = availweapons.length();
 
86
        int curidx = availweapons.find(curweapon);
 
87
        if(!num || curidx<0) return;
 
88
        int idx = (curidx+s) % num;
 
89
        if(idx<0) idx += num;
 
90
        weapon *next = availweapons[idx];
 
91
        if(next->type!=player1->weaponsel->type) // different weapon
 
92
        {
 
93
            selectweapon(next);
 
94
        }
 
95
    }
 
96
    else if(player1->isspectating()) updatefollowplayer(s);
 
97
}
 
98
 
 
99
int currentprimary() { return player1->primweap->type; }
 
100
int prevweapon() { return player1->prevweaponsel->type; }
 
101
int curweapon() { return player1->weaponsel->type; }
 
102
 
 
103
int magcontent(int w) { if(w >= 0 && w < NUMGUNS) return player1->weapons[w]->mag; else return -1;}
 
104
int magreserve(int w) { if(w >= 0 && w < NUMGUNS) return player1->weapons[w]->ammo; else return -1;}
 
105
 
 
106
COMMANDN(weapon, selectweaponi, ARG_1INT);
 
107
COMMAND(shiftweapon, ARG_1INT);
 
108
COMMAND(currentprimary, ARG_IVAL);
 
109
COMMAND(prevweapon, ARG_IVAL);
 
110
COMMAND(curweapon, ARG_IVAL);
 
111
COMMAND(magcontent, ARG_1EXP);
 
112
COMMAND(magreserve, ARG_1EXP);
 
113
 
 
114
void tryreload(playerent *p)
 
115
{
 
116
    if(!p || p->state!=CS_ALIVE || p->weaponsel->reloading || p->weaponchanging) return;
 
117
    p->weaponsel->reload();
 
118
}
 
119
 
 
120
void selfreload() { tryreload(player1); }
 
121
COMMANDN(reload, selfreload, ARG_NONE);
 
122
 
 
123
void createrays(vec &from, vec &to)             // create random spread of rays for the shotgun
 
124
{
 
125
    float f = to.dist(from)*SGSPREAD/1000;
 
126
    loopi(SGRAYS)
 
127
    {
 
128
        #define RNDD (rnd(101)-50)*f
 
129
        vec r(RNDD, RNDD, RNDD);
 
130
        sg[i] = to;
 
131
        sg[i].add(r);
 
132
        #undef RNDD
 
133
    }
 
134
}
 
135
 
 
136
static inline bool intersectbox(const vec &o, const vec &rad, const vec &from, const vec &to, vec *end) // if lineseg hits entity bounding box
 
137
{
 
138
    const vec *p;
 
139
    vec v = to, w = o;
 
140
    v.sub(from);
 
141
    w.sub(from);
 
142
    float c1 = w.dot(v);
 
143
 
 
144
    if(c1<=0) p = &from;
 
145
    else
 
146
    {
 
147
        float c2 = v.squaredlen();
 
148
        if(c2<=c1) p = &to;
 
149
        else
 
150
        {
 
151
            float f = c1/c2;
 
152
            v.mul(f).add(from);
 
153
            p = &v;
 
154
        }
 
155
    }
 
156
 
 
157
    if(p->x <= o.x+rad.x
 
158
       && p->x >= o.x-rad.x
 
159
       && p->y <= o.y+rad.y
 
160
       && p->y >= o.y-rad.y
 
161
       && p->z <= o.z+rad.z
 
162
       && p->z >= o.z-rad.z)
 
163
    {
 
164
        if(end) *end = *p;
 
165
        return true;
 
166
    }
 
167
    return false;
 
168
}
 
169
 
 
170
static inline bool intersectsphere(const vec &from, const vec &to, vec center, float radius, float &dist)
 
171
{
 
172
    vec ray(to);
 
173
    ray.sub(from);
 
174
    center.sub(from);
 
175
    float v = center.dot(ray),
 
176
          inside = radius*radius - center.squaredlen();
 
177
    if(inside < 0 && v < 0) return false;
 
178
    float raysq = ray.squaredlen(), d = inside*raysq + v*v;
 
179
    if(d < 0) return false;
 
180
    dist = (v - sqrtf(d)) / raysq;
 
181
    return dist >= 0 && dist <= 1;
 
182
}
 
183
 
 
184
static inline bool intersectcylinder(const vec &from, const vec &to, const vec &start, const vec &end, float radius, float &dist)
 
185
{
 
186
    vec d(end), m(from), n(to);
 
187
    d.sub(start);
 
188
    m.sub(start);
 
189
    n.sub(from);
 
190
    float md = m.dot(d),
 
191
          nd = n.dot(d),
 
192
          dd = d.squaredlen();
 
193
    if(md < 0 && md + nd < 0) return false;
 
194
    if(md > dd && md + nd > dd) return false;
 
195
    float nn = n.squaredlen(),
 
196
          mn = m.dot(n),
 
197
          a = dd*nn - nd*nd,
 
198
          k = m.squaredlen() - radius*radius,
 
199
          c = dd*k - md*md;
 
200
    if(fabs(a) < 0.005f)
 
201
    {
 
202
        if(c > 0) return false;
 
203
        if(md < 0) dist = -mn / nn;
 
204
        else if(md > dd) dist = (nd - mn) / nn;
 
205
        else dist = 0;
 
206
        return true;
 
207
    }
 
208
    float b = dd*mn - nd*md,
 
209
          discrim = b*b - a*c;
 
210
    if(discrim < 0) return false;
 
211
    dist = (-b - sqrtf(discrim)) / a;
 
212
    float offset = md + dist*nd;
 
213
    if(offset < 0)
 
214
    {
 
215
        if(nd < 0) return false;
 
216
        dist = -md / nd;
 
217
        if(k + dist*(2*mn + dist*nn) > 0) return false;
 
218
    }
 
219
    else if(offset > dd)
 
220
    {
 
221
        if(nd >= 0) return false;
 
222
        dist = (dd - md) / nd;
 
223
        if(k + dd - 2*md + dist*(2*(mn-nd) + dist*nn) > 0) return false;
 
224
    }
 
225
    return dist >= 0 && dist <= 1;
 
226
}
 
227
 
 
228
int intersect(playerent *d, const vec &from, const vec &to, vec *end)
 
229
{
 
230
    float dist;
 
231
    if(d->head.x >= 0)
 
232
    {
 
233
        if(intersectsphere(from, to, d->head, HEADSIZE, dist))
 
234
        {
 
235
            if(end) (*end = to).sub(from).mul(dist).add(from);
 
236
            return 2;
 
237
        }
 
238
    }
 
239
    float y = d->yaw*RAD, p = (d->pitch/4+90)*RAD, c = cosf(p);
 
240
    vec bottom(d->o), top(sinf(y)*c, -cosf(y)*c, sinf(p));
 
241
    bottom.z -= d->eyeheight;
 
242
    top.mul(d->eyeheight + d->aboveeye).add(bottom);
 
243
    if(intersectcylinder(from, to, bottom, top, d->radius, dist))
 
244
    {
 
245
        if(end) (*end = to).sub(from).mul(dist).add(from);
 
246
        return 1;
 
247
    }
 
248
    return 0;
 
249
 
 
250
#if 0
 
251
    const float eyeheight = d->eyeheight;
 
252
    vec o(d->o);
 
253
    o.z += (d->aboveeye - eyeheight)/2;
 
254
    return intersectbox(o, vec(d->radius, d->radius, (d->aboveeye + eyeheight)/2), from, to, end) ? 1 : 0;
 
255
#endif
 
256
}
 
257
 
 
258
bool intersect(entity *e, const vec &from, const vec &to, vec *end)
 
259
{
 
260
    mapmodelinfo &mmi = getmminfo(e->attr2);
 
261
    if(!&mmi || !mmi.h) return false;
 
262
 
 
263
    float lo = float(S(e->x, e->y)->floor+mmi.zoff+e->attr3);
 
264
    return intersectbox(vec(e->x, e->y, lo+mmi.h/2.0f), vec(mmi.rad, mmi.rad, mmi.h/2.0f), from, to, end);
 
265
}
 
266
 
 
267
playerent *intersectclosest(const vec &from, const vec &to, playerent *at, int &hitzone, bool aiming = true)
 
268
{
 
269
    playerent *best = NULL;
 
270
    float bestdist = 1e16f;
 
271
    int zone;
 
272
    if(at!=player1 && player1->state==CS_ALIVE && (zone = intersect(player1, from, to)))
 
273
    {
 
274
        best = player1;
 
275
        bestdist = at->o.dist(player1->o);
 
276
        hitzone = zone;
 
277
    }
 
278
    loopv(players)
 
279
    {
 
280
        playerent *o = players[i];
 
281
        if(!o || o==at || (o->state!=CS_ALIVE && (aiming || (o->state!=CS_EDITING && o->state!=CS_LAGGED)))) continue;
 
282
        float dist = at->o.dist(o->o);
 
283
        if(dist < bestdist && (zone = intersect(o, from, to)))
 
284
        {
 
285
            best = o;
 
286
            bestdist = dist;
 
287
            hitzone = zone;
 
288
        }
 
289
    }
 
290
    return best;
 
291
}
 
292
 
 
293
playerent *playerincrosshair()
 
294
{
 
295
    if(camera1->type == ENT_PLAYER || (camera1->type == ENT_CAMERA && player1->spectatemode == SM_DEATHCAM))
 
296
    {
 
297
        int hitzone;
 
298
        return intersectclosest(camera1->o, worldpos, (playerent *)camera1, hitzone, false);
 
299
    }
 
300
    else return NULL;
 
301
}
 
302
 
 
303
void damageeffect(int damage, playerent *d)
 
304
{
 
305
    particle_splash(3, damage/10, 1000, d->o);
 
306
}
 
307
 
 
308
 
 
309
vector<hitmsg> hits;
 
310
 
 
311
void hit(int damage, playerent *d, playerent *at, const vec &vel, int gun, bool gib, int info)
 
312
{
 
313
    if(d==player1 || d->type==ENT_BOT || !m_mp(gamemode)) d->hitpush(damage, vel, at, gun);
 
314
 
 
315
    if(!m_mp(gamemode)) dodamage(damage, d, at, gib);
 
316
    else
 
317
    {
 
318
        hitmsg &h = hits.add();
 
319
        h.target = d->clientnum;
 
320
        h.lifesequence = d->lifesequence;
 
321
        h.info = info;
 
322
        if(d==player1)
 
323
        {
 
324
            h.dir = ivec(0, 0, 0);
 
325
            d->damageroll(damage);
 
326
            updatedmgindicator(at->o);
 
327
            damageblend(damage);
 
328
            damageeffect(damage, d);
 
329
            playsound(S_PAIN6, SP_HIGH);
 
330
        }
 
331
        else
 
332
        {
 
333
            h.dir = ivec(int(vel.x*DNF), int(vel.y*DNF), int(vel.z*DNF));
 
334
            damageeffect(damage, d);
 
335
            playsound(S_PAIN1+rnd(5), d);
 
336
        }
 
337
    }
 
338
}
 
339
 
 
340
void hitpush(int damage, playerent *d, playerent *at, vec &from, vec &to, int gun, bool gib, int info)
 
341
{
 
342
    vec v(to);
 
343
    v.sub(from);
 
344
    v.normalize();
 
345
    hit(damage, d, at, v, gun, gib, info);
 
346
}
 
347
 
 
348
float expdist(playerent *o, vec &dir, const vec &v)
 
349
{
 
350
    vec middle = o->o;
 
351
    middle.z += (o->aboveeye-o->eyeheight)/2;
 
352
    float dist = middle.dist(v, dir);
 
353
    dir.div(dist);
 
354
    if(dist<0) dist = 0;
 
355
    return dist;
 
356
}
 
357
 
 
358
void radialeffect(playerent *o, vec &v, int qdam, playerent *at, int gun)
 
359
{
 
360
    if(o->state!=CS_ALIVE) return;
 
361
    vec dir;
 
362
    float dist = expdist(o, dir, v);
 
363
    if(dist<EXPDAMRAD)
 
364
    {
 
365
        int damage = (int)(qdam*(1-dist/EXPDAMRAD));
 
366
        hit(damage, o, at, dir, gun, true, int(dist*DMF));
 
367
    }
 
368
}
 
369
 
 
370
vector<bounceent *> bounceents;
 
371
 
 
372
void removebounceents(playerent *owner)
 
373
{
 
374
    loopv(bounceents) if(bounceents[i]->owner==owner) { delete bounceents[i]; bounceents.remove(i--); }
 
375
}
 
376
 
 
377
void movebounceents()
 
378
{
 
379
    loopv(bounceents) if(bounceents[i])
 
380
    {
 
381
        bounceent *p = bounceents[i];
 
382
        if((p->bouncetype==BT_NADE || p->bouncetype==BT_GIB) && p->applyphysics()) movebounceent(p, 1, false);
 
383
        if(!p->isalive(lastmillis))
 
384
        {
 
385
            p->destroy();
 
386
            delete p;
 
387
            bounceents.remove(i--);
 
388
        }
 
389
    }
 
390
}
 
391
 
 
392
void clearbounceents()
 
393
{
 
394
    if(gamespeed==100);
 
395
    else if(multiplayer(false)) bounceents.add((bounceent *)player1);
 
396
    loopv(bounceents) if(bounceents[i]) { delete bounceents[i]; bounceents.remove(i--); }
 
397
}
 
398
 
 
399
void renderbounceents()
 
400
{
 
401
    loopv(bounceents)
 
402
    {
 
403
        bounceent *p = bounceents[i];
 
404
        if(!p) continue;
 
405
        string model;
 
406
        vec o(p->o);
 
407
 
 
408
        int anim = ANIM_MAPMODEL, basetime = 0;
 
409
        switch(p->bouncetype)
 
410
        {
 
411
            case BT_NADE:
 
412
                s_strcpy(model, "weapons/grenade/static");
 
413
                break;
 
414
            case BT_GIB:
 
415
            default:
 
416
            {
 
417
                uint n = (((4*(uint)(size_t)p)+(uint)p->timetolive)%3)+1;
 
418
                s_sprintf(model)("misc/gib0%u", n);
 
419
                int t = lastmillis-p->millis;
 
420
                if(t>p->timetolive-2000)
 
421
                {
 
422
                    anim = ANIM_DECAY;
 
423
                    basetime = p->millis+p->timetolive-2000;
 
424
                    t -= p->timetolive-2000;
 
425
                    o.z -= t*t/4000000000.0f*t;
 
426
                }
 
427
                break;
 
428
            }
 
429
        }
 
430
        path(model);
 
431
        rendermodel(model, anim|ANIM_LOOP|ANIM_DYNALLOC, 0, 1.1f, o, p->yaw+90, p->pitch, 0, basetime);
 
432
    }
 
433
}
 
434
 
 
435
VARP(gib, 0, 1, 1);
 
436
VARP(gibnum, 0, 6, 1000);
 
437
VARP(gibttl, 0, 7000, 60000);
 
438
VARP(gibspeed, 1, 30, 100);
 
439
 
 
440
void addgib(playerent *d)
 
441
{
 
442
    if(!d || !gib || !gibttl) return;
 
443
    playsound(S_GIB, d);
 
444
 
 
445
    loopi(gibnum)
 
446
    {
 
447
        bounceent *p = bounceents.add(new bounceent);
 
448
        p->owner = d;
 
449
        p->millis = lastmillis;
 
450
        p->timetolive = gibttl+rnd(10)*100;
 
451
        p->bouncetype = BT_GIB;
 
452
 
 
453
        p->o = d->o;
 
454
        p->o.z -= d->aboveeye;
 
455
        p->inwater = hdr.waterlevel>p->o.z;
 
456
 
 
457
        p->yaw = (float)rnd(360);
 
458
        p->pitch = (float)rnd(360);
 
459
 
 
460
        p->maxspeed = 30.0f;
 
461
        p->rotspeed = 3.0f;
 
462
 
 
463
        const float angle = (float)rnd(360);
 
464
        const float speed = (float)gibspeed;
 
465
 
 
466
        p->vel.x = sinf(RAD*angle)*rnd(1000)/1000.0f;
 
467
        p->vel.y = cosf(RAD*angle)*rnd(1000)/1000.0f;
 
468
        p->vel.z = rnd(1000)/1000.0f;
 
469
        p->vel.mul(speed/100.0f);
 
470
 
 
471
        p->resetinterp();
 
472
    }
 
473
}
 
474
 
 
475
void shorten(vec &from, vec &to, vec &target)
 
476
{
 
477
    target.sub(from).normalize().mul(from.dist(to)).add(from);
 
478
}
 
479
 
 
480
void raydamage(vec &from, vec &to, playerent *d)
 
481
{
 
482
    int dam = d->weaponsel->info.damage;
 
483
    int hitzone = -1;
 
484
    playerent *o = NULL;
 
485
 
 
486
    if(d->weaponsel->type==GUN_SHOTGUN)
 
487
    {
 
488
        uint done = 0;
 
489
        playerent *cl = NULL;
 
490
        for(;;)
 
491
        {
 
492
            bool raysleft = false;
 
493
            int hitrays = 0;
 
494
            o = NULL;
 
495
            loop(r, SGRAYS) if((done&(1<<r))==0 && (cl = intersectclosest(from, sg[r], d, hitzone)))
 
496
            {
 
497
                if(!o || o==cl)
 
498
                {
 
499
                    hitrays++;
 
500
                    o = cl;
 
501
                    done |= 1<<r;
 
502
                    shorten(from, o->o, sg[r]);
 
503
                }
 
504
                else raysleft = true;
 
505
            }
 
506
            if(hitrays) hitpush(hitrays*dam, o, d, from, to, d->weaponsel->type, false, hitrays);
 
507
            if(!raysleft) break;
 
508
        }
 
509
    }
 
510
    else if((o = intersectclosest(from, to, d, hitzone)))
 
511
    {
 
512
        bool gib = false;
 
513
        if(d->weaponsel->type==GUN_KNIFE) gib = true;
 
514
        else if(d==player1 && d->weaponsel->type==GUN_SNIPER && hitzone==2)
 
515
        {
 
516
            dam *= 3;
 
517
            gib = true;
 
518
        }
 
519
 
 
520
        hitpush(dam, o, d, from, to, d->weaponsel->type, gib, gib ? 1 : 0);
 
521
        shorten(from, o->o, to);
 
522
    }
 
523
}
 
524
 
 
525
// weapon
 
526
 
 
527
weapon::weapon(struct playerent *owner, int type) : type(type), owner(owner), info(guns[type]),
 
528
    ammo(owner->ammo[type]), mag(owner->mag[type]), gunwait(owner->gunwait[type]), reloading(0)
 
529
{
 
530
}
 
531
 
 
532
const int weapon::weaponchangetime = 400;
 
533
const float weapon::weaponbeloweye = 0.2f;
 
534
 
 
535
int weapon::flashtime() const { return max((int)info.attackdelay, 120)/4; }
 
536
 
 
537
void weapon::sendshoot(vec &from, vec &to)
 
538
{
 
539
    if(owner!=player1) return;
 
540
    addmsg(SV_SHOOT, "ri2i6iv", lastmillis, owner->weaponsel->type,
 
541
           (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF),
 
542
           (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF),
 
543
           hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
 
544
}
 
545
 
 
546
bool weapon::modelattacking()
 
547
{
 
548
    int animtime = min(owner->gunwait[owner->weaponsel->type], (int)owner->weaponsel->info.attackdelay);
 
549
    if(lastmillis - owner->lastaction < animtime) return true;
 
550
    else return false;
 
551
}
 
552
 
 
553
void weapon::attacksound()
 
554
{
 
555
    if(info.sound == S_NULL) return;
 
556
    bool local = (owner == player1);
 
557
    playsound(info.sound, owner, local ? SP_HIGH : SP_NORMAL);
 
558
}
 
559
 
 
560
bool weapon::reload()
 
561
{
 
562
    if(mag>=info.magsize || ammo<=0) return false;
 
563
    updatelastaction(owner);
 
564
    reloading = lastmillis;
 
565
    gunwait += info.reloadtime;
 
566
 
 
567
    int numbullets = min(info.magsize - mag, ammo);
 
568
        mag += numbullets;
 
569
        ammo -= numbullets;
 
570
 
 
571
    bool local = (player1 == owner);
 
572
        if(owner->type==ENT_BOT) playsound(info.reload, owner);
 
573
    else playsoundc(info.reload);
 
574
    if(local) addmsg(SV_RELOAD, "ri2", lastmillis, owner->weaponsel->type);
 
575
    return true;
 
576
}
 
577
 
 
578
void weapon::renderstats()
 
579
{
 
580
    char gunstats[64];
 
581
    sprintf(gunstats, "%i/%i", mag, ammo);
 
582
    draw_text(gunstats, 690, 827);
 
583
}
 
584
 
 
585
//VAR(recoiltest, 0, 0, 1); // FIXME ON RELEASE
 
586
int recoiltest = 0;
 
587
 
 
588
VAR(recoilincrease, 1, 2, 10);
 
589
VAR(recoilbase, 0, 40, 1000);
 
590
VAR(maxrecoil, 0, 1000, 1000);
 
591
 
 
592
void weapon::attackphysics(vec &from, vec &to) // physical fx to the owner
 
593
{
 
594
    const guninfo &g = info;
 
595
    vec unitv;
 
596
    float dist = to.dist(from, unitv);
 
597
    float f = dist/1000;
 
598
    int spread = dynspread();
 
599
    float recoil = dynrecoil()*-0.01f;
 
600
 
 
601
    // spread
 
602
    if(spread>1)
 
603
    {
 
604
        #define RNDD (rnd(spread)-spread/2)*f
 
605
        vec r(RNDD, RNDD, RNDD);
 
606
        to.add(r);
 
607
        #undef RNDD
 
608
    }
 
609
    // kickback
 
610
    owner->vel.add(vec(unitv).mul(recoil/dist).mul(owner->crouching ? 0.75 : 1.0f));
 
611
    // recoil
 
612
    int numshots = info.isauto ? shots : 1;
 
613
    if(recoiltest)
 
614
    {
 
615
        owner->pitchvel = min(powf(numshots/(float)(recoilincrease), 2.0f)+(float)(recoilbase)/10.0f, (float)(maxrecoil)/10.0f);
 
616
    }
 
617
    else
 
618
    {
 
619
        owner->pitchvel = min(powf(numshots/(float)(g.recoilincrease), 2.0f)+(float)(g.recoilbase)/10.0f, (float)(g.maxrecoil)/10.0f);
 
620
    }
 
621
}
 
622
 
 
623
void weapon::renderhudmodel(int lastaction, int index)
 
624
{
 
625
    vec unitv;
 
626
    float dist = worldpos.dist(owner->o, unitv);
 
627
    unitv.div(dist);
 
628
 
 
629
        weaponmove wm;
 
630
        if(!intermission) wm.calcmove(unitv, lastaction);
 
631
    s_sprintfd(path)("weapons/%s", info.modelname);
 
632
    bool emit = (wm.anim&ANIM_INDEX)==ANIM_GUN_SHOOT && (lastmillis - lastaction) < flashtime();
 
633
    rendermodel(path, wm.anim|ANIM_DYNALLOC|(index ? ANIM_MIRROR : 0)|(emit ? ANIM_PARTICLE : 0), 0, -1, wm.pos, player1->yaw+90, player1->pitch+wm.k_rot, 40.0f, wm.basetime, NULL, NULL, 1.28f);
 
634
}
 
635
 
 
636
void weapon::updatetimers()
 
637
{
 
638
    if(gunwait) gunwait = max(gunwait - (lastmillis-owner->lastaction), 0);
 
639
}
 
640
 
 
641
void weapon::onselecting()
 
642
{
 
643
    updatelastaction(owner);
 
644
    playsound(S_GUNCHANGE, owner == player1? SP_HIGH : SP_NORMAL);
 
645
}
 
646
 
 
647
void weapon::renderhudmodel() { renderhudmodel(owner->lastaction); }
 
648
void weapon::renderaimhelp(bool teamwarning) { drawcrosshair(owner, teamwarning ? CROSSHAIR_TEAMMATE : CROSSHAIR_DEFAULT); }
 
649
int weapon::dynspread() { return info.spread; }
 
650
float weapon::dynrecoil() { return info.recoil; }
 
651
bool weapon::selectable() { return this != owner->weaponsel && owner->state == CS_ALIVE && !owner->weaponchanging; }
 
652
bool weapon::deselectable() { return !reloading; }
 
653
 
 
654
void weapon::equipplayer(playerent *pl)
 
655
{
 
656
    if(!pl) return;
 
657
    pl->weapons[GUN_ASSAULT] = new assaultrifle(pl);
 
658
    pl->weapons[GUN_GRENADE] = new grenades(pl);
 
659
    pl->weapons[GUN_KNIFE] = new knife(pl);
 
660
    pl->weapons[GUN_PISTOL] = new pistol(pl);
 
661
    pl->weapons[GUN_SHOTGUN] = new shotgun(pl);
 
662
    pl->weapons[GUN_SNIPER] = new sniperrifle(pl);
 
663
    pl->weapons[GUN_SUBGUN] = new subgun(pl);
 
664
    pl->weapons[GUN_AKIMBO] = new akimbo(pl);
 
665
    pl->selectweapon(GUN_ASSAULT);
 
666
    pl->setprimary(GUN_ASSAULT);
 
667
    pl->setnextprimary(GUN_ASSAULT);
 
668
}
 
669
 
 
670
bool weapon::valid(int id) { return id>=0 && id<NUMGUNS; }
 
671
 
 
672
// grenadeent
 
673
 
 
674
enum { NS_NONE, NS_ACTIVATED = 0, NS_THROWED, NS_EXPLODED };
 
675
 
 
676
grenadeent::grenadeent(playerent *owner, int millis)
 
677
{
 
678
    ASSERT(owner);
 
679
    nadestate = NS_NONE;
 
680
    local = owner==player1;
 
681
    bounceent::owner = owner;
 
682
    bounceent::millis = lastmillis;
 
683
    timetolive = 2000-millis;
 
684
    bouncetype = BT_NADE;
 
685
    maxspeed = 30.0f;
 
686
    rotspeed = 6.0f;
 
687
    distsincebounce = 0.0f;
 
688
}
 
689
 
 
690
grenadeent::~grenadeent()
 
691
{
 
692
    if(owner && owner->weapons[GUN_GRENADE]) owner->weapons[GUN_GRENADE]->removebounceent(this);
 
693
}
 
694
 
 
695
void grenadeent::explode()
 
696
{
 
697
    if(nadestate!=NS_ACTIVATED && nadestate!=NS_THROWED ) return;
 
698
    nadestate = NS_EXPLODED;
 
699
    static vec n(0,0,0);
 
700
    hits.setsizenodelete(0);
 
701
    splash();
 
702
    if(local)
 
703
        addmsg(SV_EXPLODE, "ri3iv", lastmillis, GUN_GRENADE, millis, // fixme
 
704
            hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
 
705
    playsound(S_FEXPLODE, &o);
 
706
}
 
707
 
 
708
void grenadeent::splash()
 
709
{
 
710
    particle_splash(0, 50, 300, o);
 
711
    particle_fireball(5, o);
 
712
    addscorchmark(o);
 
713
    adddynlight(NULL, o, 16, 200, 100, 255, 255, 224);
 
714
    adddynlight(NULL, o, 16, 600, 600, 192, 160, 128);
 
715
    if(owner != player1) return;
 
716
    int damage = guns[GUN_GRENADE].damage;
 
717
    radialeffect(owner, o, damage, owner, GUN_GRENADE);
 
718
    loopv(players)
 
719
    {
 
720
        playerent *p = players[i];
 
721
        if(!p) continue;
 
722
        radialeffect(p, o, damage, owner, GUN_GRENADE);
 
723
    }
 
724
}
 
725
 
 
726
void grenadeent::activate(const vec &from, const vec &to)
 
727
{
 
728
    if(nadestate!=NS_NONE) return;
 
729
    nadestate = NS_ACTIVATED;
 
730
 
 
731
    if(local)
 
732
    {
 
733
        addmsg(SV_SHOOT, "ri2i6i", millis, owner->weaponsel->type,
 
734
               (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF),
 
735
               (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF),
 
736
               0);
 
737
        playsound(S_GRENADEPULL, SP_HIGH);
 
738
    }
 
739
}
 
740
 
 
741
void grenadeent::_throw(const vec &from, const vec &vel)
 
742
{
 
743
    if(nadestate!=NS_ACTIVATED) return;
 
744
    nadestate = NS_THROWED;
 
745
    this->vel = vel;
 
746
    this->o = from;
 
747
    this->resetinterp();
 
748
    inwater = hdr.waterlevel>o.z;
 
749
 
 
750
    if(local)
 
751
    {
 
752
        addmsg(SV_THROWNADE, "ri7", int(o.x*DMF), int(o.y*DMF), int(o.z*DMF), int(vel.x*DMF), int(vel.y*DMF), int(vel.z*DMF), lastmillis-millis);
 
753
        playsound(S_GRENADETHROW, SP_HIGH);
 
754
    }
 
755
    else playsound(S_GRENADETHROW, owner);
 
756
}
 
757
 
 
758
void grenadeent::moveoutsidebbox(const vec &direction, playerent *boundingbox)
 
759
{
 
760
    vel = direction;
 
761
    o = boundingbox->o;
 
762
    inwater = hdr.waterlevel>o.z;
 
763
 
 
764
    boundingbox->cancollide = false;
 
765
    loopi(10) moveplayer(this, 10, true, 10);
 
766
    boundingbox->cancollide = true;
 
767
}
 
768
 
 
769
void grenadeent::destroy() { explode(); }
 
770
bool grenadeent::applyphysics() { return nadestate==NS_THROWED; }
 
771
 
 
772
void grenadeent::oncollision()
 
773
{
 
774
    if(distsincebounce>=1.5f) playsound(S_GRENADEBOUNCE1+rnd(2), &o);
 
775
    distsincebounce = 0.0f;
 
776
}
 
777
 
 
778
void grenadeent::onmoved(const vec &dist)
 
779
{
 
780
    distsincebounce += dist.magnitude();
 
781
}
 
782
 
 
783
// grenades
 
784
 
 
785
grenades::grenades(playerent *owner) : weapon(owner, GUN_GRENADE), inhandnade(NULL), throwwait((13*1000)/40), throwmillis(0), state(GST_NONE) {}
 
786
 
 
787
int grenades::flashtime() const { return 0; }
 
788
 
 
789
bool grenades::busy() { return state!=GST_NONE; }
 
790
 
 
791
bool grenades::attack(vec &targ)
 
792
{
 
793
    int attackmillis = lastmillis-owner->lastaction;
 
794
    vec &to = targ;
 
795
 
 
796
    bool waitdone = attackmillis>=gunwait;
 
797
    if(waitdone) gunwait = reloading = 0;
 
798
 
 
799
    switch(state)
 
800
    {
 
801
        case GST_NONE:
 
802
            if(waitdone && owner->attacking && this==owner->weaponsel) activatenade(to); // activate
 
803
            break;
 
804
 
 
805
        case GST_INHAND:
 
806
            if(waitdone)
 
807
            {
 
808
                if(!owner->attacking || this!=owner->weaponsel) thrownade(); // throw
 
809
                else if(!inhandnade->isalive(lastmillis)) dropnade(); // drop & have fun
 
810
            }
 
811
            break;
 
812
 
 
813
        case GST_THROWING:
 
814
            if(attackmillis >= throwwait) // throw done
 
815
            {
 
816
                reset();
 
817
                if(!mag && this==owner->weaponsel) // switch to primary immediately
 
818
                {
 
819
                    owner->weaponchanging = lastmillis-1-(weaponchangetime/2);
 
820
                    owner->nextweaponsel = owner->weaponsel = owner->primweap;
 
821
                }
 
822
                return false;
 
823
            }
 
824
            break;
 
825
    }
 
826
    return true;
 
827
}
 
828
 
 
829
void grenades::attackfx(const vec &from, const vec &to, int millis) // other player's grenades
 
830
{
 
831
    throwmillis = lastmillis-millis;
 
832
    if(millis == 0) playsound(S_GRENADEPULL, owner); // activate
 
833
    else if(millis > 0) // throw
 
834
    {
 
835
        grenadeent *g = new grenadeent(owner, millis);
 
836
        bounceents.add(g);
 
837
        g->_throw(from, to);
 
838
    }
 
839
}
 
840
 
 
841
int grenades::modelanim()
 
842
{
 
843
    if(state == GST_THROWING) return ANIM_GUN_THROW;
 
844
    else
 
845
    {
 
846
        int animtime = min(gunwait, (int)info.attackdelay);
 
847
        if(state == GST_INHAND || lastmillis - owner->lastaction < animtime) return ANIM_GUN_SHOOT;
 
848
    }
 
849
    return ANIM_GUN_IDLE;
 
850
}
 
851
 
 
852
void grenades::activatenade(const vec &to)
 
853
{
 
854
    if(!mag) return;
 
855
    throwmillis = 0;
 
856
 
 
857
    inhandnade = new grenadeent(owner);
 
858
    bounceents.add(inhandnade);
 
859
 
 
860
    updatelastaction(owner);
 
861
    mag--;
 
862
    gunwait = info.attackdelay;
 
863
    owner->lastattackweapon = this;
 
864
    state = GST_INHAND;
 
865
    inhandnade->activate(owner->o, to);
 
866
}
 
867
 
 
868
void grenades::thrownade()
 
869
{
 
870
    if(!inhandnade) return;
 
871
    const float speed = cosf(RAD*owner->pitch);
 
872
    vec vel(sinf(RAD*owner->yaw)*speed, -cosf(RAD*owner->yaw)*speed, sinf(RAD*owner->pitch));
 
873
    vel.mul(1.5f);
 
874
    thrownade(vel);
 
875
}
 
876
 
 
877
void grenades::thrownade(const vec &vel)
 
878
{
 
879
    inhandnade->moveoutsidebbox(vel, owner);
 
880
    inhandnade->_throw(inhandnade->o, vel);
 
881
    inhandnade = NULL;
 
882
 
 
883
    throwmillis = lastmillis;
 
884
    updatelastaction(owner);
 
885
    state = GST_THROWING;
 
886
    if(this==owner->weaponsel) owner->attacking = false;
 
887
}
 
888
 
 
889
void grenades::dropnade()
 
890
{
 
891
    vec n(0,0,0);
 
892
    thrownade(n);
 
893
}
 
894
 
 
895
void grenades::renderstats()
 
896
{
 
897
    char gunstats[64];
 
898
    sprintf(gunstats, "%i", mag);
 
899
    draw_text(gunstats, 690, 827);
 
900
}
 
901
 
 
902
bool grenades::selectable() { return weapon::selectable() && state != GST_INHAND && mag; }
 
903
void grenades::reset() { throwmillis = 0; state = GST_NONE; }
 
904
 
 
905
void grenades::onselecting() { reset(); playsound(S_GUNCHANGE); }
 
906
void grenades::onownerdies()
 
907
{
 
908
    reset();
 
909
    if(owner==player1 && inhandnade) dropnade();
 
910
}
 
911
 
 
912
void grenades::removebounceent(bounceent *b)
 
913
{
 
914
    if(b == inhandnade) { inhandnade = NULL; reset(); }
 
915
}
 
916
 
 
917
// gun base class
 
918
 
 
919
gun::gun(playerent *owner, int type) : weapon(owner, type) {}
 
920
 
 
921
bool gun::attack(vec &targ)
 
922
{
 
923
    int attackmillis = lastmillis-owner->lastaction;
 
924
    if(timebalance < gunwait) attackmillis += timebalance;
 
925
        if(attackmillis<gunwait) return false;
 
926
        timebalance = gunwait ? attackmillis - gunwait : 0;
 
927
    gunwait = reloading = 0;
 
928
 
 
929
    if(!owner->attacking)
 
930
    {
 
931
        shots = 0;
 
932
        checkautoreload();
 
933
        return false;
 
934
    }
 
935
 
 
936
    updatelastaction(owner);
 
937
    if(!mag)
 
938
    {
 
939
        playsoundc(S_NOAMMO);
 
940
            gunwait += 250;
 
941
            owner->lastattackweapon = NULL;
 
942
        shots = 0;
 
943
        checkautoreload();
 
944
        return false;
 
945
    }
 
946
 
 
947
    owner->lastattackweapon = this;
 
948
        shots++;
 
949
 
 
950
        if(!info.isauto) owner->attacking = false;
 
951
 
 
952
    vec from = owner->o;
 
953
    vec to = targ;
 
954
    from.z -= weaponbeloweye;
 
955
 
 
956
    attackphysics(from, to);
 
957
 
 
958
    hits.setsizenodelete(0);
 
959
    raydamage(from, to, owner);
 
960
    attackfx(from, to, 0);
 
961
 
 
962
    gunwait = info.attackdelay;
 
963
    mag--;
 
964
 
 
965
    sendshoot(from, to);
 
966
    return true;
 
967
}
 
968
 
 
969
void gun::attackfx(const vec &from, const vec &to, int millis)
 
970
{
 
971
    addbullethole(owner, from, to);
 
972
    addshotline(owner, from, to);
 
973
    particle_splash(0, 5, 250, to);
 
974
    adddynlight(owner, from, 4, 100, 50, 96, 80, 64);
 
975
    attacksound();
 
976
}
 
977
 
 
978
int gun::modelanim() { return modelattacking() ? ANIM_GUN_SHOOT|ANIM_LOOP : ANIM_GUN_IDLE; }
 
979
void gun::checkautoreload() { if(autoreload && owner==player1 && !mag) reload(); }
 
980
 
 
981
 
 
982
// shotgun
 
983
 
 
984
shotgun::shotgun(playerent *owner) : gun(owner, GUN_SHOTGUN) {}
 
985
 
 
986
bool shotgun::attack(vec &targ)
 
987
{
 
988
    vec from = owner->o;
 
989
        from.z -= weaponbeloweye;
 
990
    createrays(from, targ);
 
991
    return gun::attack(targ);
 
992
}
 
993
 
 
994
void shotgun::attackfx(const vec &from, const vec &to, int millis)
 
995
{
 
996
    loopi(SGRAYS) particle_splash(0, 5, 200, sg[i]);
 
997
    if(addbullethole(owner, from, to))
 
998
    {
 
999
        int holes = 3+rnd(5);
 
1000
        loopi(holes) addbullethole(owner, from, sg[i], 0, false);
 
1001
    }
 
1002
    adddynlight(owner, from, 4, 100, 50, 96, 80, 64);
 
1003
    attacksound();
 
1004
}
 
1005
 
 
1006
bool shotgun::selectable() { return weapon::selectable() && !m_noprimary && this == owner->primweap; }
 
1007
 
 
1008
 
 
1009
// subgun
 
1010
 
 
1011
subgun::subgun(playerent *owner) : gun(owner, GUN_SUBGUN) {}
 
1012
bool subgun::selectable() { return weapon::selectable() && !m_noprimary && this == owner->primweap; }
 
1013
 
 
1014
 
 
1015
// sniperrifle
 
1016
 
 
1017
sniperrifle::sniperrifle(playerent *owner) : gun(owner, GUN_SNIPER), scoped(false) {}
 
1018
 
 
1019
void sniperrifle::attackfx(const vec &from, const vec &to, int millis)
 
1020
{
 
1021
    addbullethole(owner, from, to);
 
1022
    addshotline(owner, from, to);
 
1023
    particle_splash(0, 50, 200, to);
 
1024
    particle_trail(1, 500, from, to);
 
1025
    adddynlight(owner, from, 4, 100, 50, 96, 80, 64);
 
1026
    attacksound();
 
1027
}
 
1028
 
 
1029
bool sniperrifle::reload()
 
1030
{
 
1031
    bool r = weapon::reload();
 
1032
    if(owner==player1 && r) scoped = false;
 
1033
    return r;
 
1034
}
 
1035
 
 
1036
#define SCOPESETTLETIME 100
 
1037
int sniperrifle::dynspread()
 
1038
{
 
1039
    if(scoped)
 
1040
    {
 
1041
        int scopetime = lastmillis - scoped_since;
 
1042
        if(scopetime > SCOPESETTLETIME)
 
1043
            return 1;
 
1044
        else
 
1045
            return max((info.spread * (SCOPESETTLETIME - scopetime)) / SCOPESETTLETIME, 1);
 
1046
    }
 
1047
    return info.spread;
 
1048
}
 
1049
float sniperrifle::dynrecoil() { return scoped && lastmillis - scoped_since > SCOPESETTLETIME ? info.recoil / 3 : info.recoil; }
 
1050
bool sniperrifle::selectable() { return weapon::selectable() && !m_noprimary && this == owner->primweap; }
 
1051
void sniperrifle::onselecting() { weapon::onselecting(); scoped = false; }
 
1052
void sniperrifle::ondeselecting() { scoped = false; }
 
1053
void sniperrifle::onownerdies() { scoped = false; }
 
1054
void sniperrifle::renderhudmodel() { if(!scoped) weapon::renderhudmodel(); }
 
1055
 
 
1056
void sniperrifle::renderaimhelp(bool teamwarning)
 
1057
{
 
1058
    if(scoped) drawscope();
 
1059
    if(scoped || teamwarning) drawcrosshair(owner, teamwarning ? CROSSHAIR_TEAMMATE : CROSSHAIR_SCOPE, NULL, 24.0f);
 
1060
}
 
1061
 
 
1062
void sniperrifle::setscope(bool enable)
 
1063
{
 
1064
    if(this == owner->weaponsel && !reloading && owner->state == CS_ALIVE)
 
1065
    {
 
1066
        if(scoped == false && enable == true) scoped_since = lastmillis;
 
1067
        scoped = enable;
 
1068
    }
 
1069
}
 
1070
 
 
1071
 
 
1072
// assaultrifle
 
1073
 
 
1074
assaultrifle::assaultrifle(playerent *owner) : gun(owner, GUN_ASSAULT) {}
 
1075
 
 
1076
int assaultrifle::dynspread() { return shots > 3 ? 70 : info.spread; }
 
1077
float assaultrifle::dynrecoil() { return info.recoil + (rnd(8)*-0.01f); }
 
1078
bool assaultrifle::selectable() { return weapon::selectable() && !m_noprimary && this == owner->primweap; }
 
1079
 
 
1080
 
 
1081
// pistol
 
1082
 
 
1083
pistol::pistol(playerent *owner) : gun(owner, GUN_PISTOL) {}
 
1084
bool pistol::selectable() { return weapon::selectable() && !m_nopistol; }
 
1085
 
 
1086
 
 
1087
// akimbo
 
1088
 
 
1089
akimbo::akimbo(playerent *owner) : gun(owner, GUN_AKIMBO), akimbomillis(0)
 
1090
{
 
1091
    akimbolastaction[0] = akimbolastaction[1] = 0;
 
1092
}
 
1093
 
 
1094
bool akimbo::attack(vec &targ)
 
1095
{
 
1096
    if(gun::attack(targ))
 
1097
    {
 
1098
                akimbolastaction[akimboside?1:0] = lastmillis;
 
1099
                akimboside = !akimboside;
 
1100
        return true;
 
1101
    }
 
1102
    return false;
 
1103
}
 
1104
 
 
1105
void akimbo::onammopicked()
 
1106
{
 
1107
    akimbomillis = lastmillis + 30000;
 
1108
    if(owner==player1)
 
1109
    {
 
1110
        if(owner->weaponsel->type!=GUN_SNIPER && owner->weaponsel->type!=GUN_GRENADE) owner->weaponswitch(this);
 
1111
        addmsg(SV_AKIMBO, "ri", lastmillis);
 
1112
    }
 
1113
}
 
1114
 
 
1115
void akimbo::onselecting()
 
1116
{
 
1117
    gun::onselecting();
 
1118
    akimbolastaction[0] = akimbolastaction[1] = lastmillis;
 
1119
}
 
1120
 
 
1121
bool akimbo::selectable() { return weapon::selectable() && !m_nopistol && owner->akimbo; }
 
1122
void akimbo::updatetimers() { weapon::updatetimers(); /*loopi(2) akimbolastaction[i] = lastmillis;*/ }
 
1123
void akimbo::reset() { akimbolastaction[0] = akimbolastaction[1] = akimbomillis = 0; akimboside = false; }
 
1124
 
 
1125
void akimbo::renderhudmodel()
 
1126
{
 
1127
    weapon::renderhudmodel(akimbolastaction[0], 0);
 
1128
    weapon::renderhudmodel(akimbolastaction[1], 1);
 
1129
}
 
1130
 
 
1131
bool akimbo::timerout() { return akimbomillis && akimbomillis <= lastmillis; }
 
1132
 
 
1133
 
 
1134
// knife
 
1135
 
 
1136
knife::knife(playerent *owner) : weapon(owner, GUN_KNIFE) {}
 
1137
 
 
1138
int knife::flashtime() const { return 0; }
 
1139
 
 
1140
bool knife::attack(vec &targ)
 
1141
{
 
1142
    int attackmillis = lastmillis-owner->lastaction;
 
1143
        if(attackmillis<gunwait) return false;
 
1144
    gunwait = reloading = 0;
 
1145
 
 
1146
    if(!owner->attacking) return false;
 
1147
    updatelastaction(owner);
 
1148
 
 
1149
    owner->lastattackweapon = this;
 
1150
        owner->attacking = false;
 
1151
 
 
1152
    vec from = owner->o;
 
1153
    vec to = targ;
 
1154
    from.z -= weaponbeloweye;
 
1155
 
 
1156
    vec unitv;
 
1157
    float dist = to.dist(from, unitv);
 
1158
    unitv.div(dist);
 
1159
    unitv.mul(3); // punch range
 
1160
    to = from;
 
1161
    to.add(unitv);
 
1162
 
 
1163
    hits.setsizenodelete(0);
 
1164
    raydamage(from, to, owner);
 
1165
    attackfx(from, to, 0);
 
1166
    sendshoot(from, to);
 
1167
    gunwait = info.attackdelay;
 
1168
    return true;
 
1169
}
 
1170
 
 
1171
int knife::modelanim() { return modelattacking() ? ANIM_GUN_SHOOT : ANIM_GUN_IDLE; }
 
1172
 
 
1173
void knife::drawstats() {}
 
1174
void knife::attackfx(const vec &from, const vec &to, int millis) { attacksound(); }
 
1175
void knife::renderstats() { }
 
1176
 
 
1177
 
 
1178
void setscope(bool enable)
 
1179
{
 
1180
    if(player1->weaponsel->type != GUN_SNIPER) return;
 
1181
    sniperrifle *sr = (sniperrifle *)player1->weaponsel;
 
1182
    sr->setscope(enable);
 
1183
}
 
1184
 
 
1185
COMMAND(setscope, ARG_1INT);
 
1186
 
 
1187
 
 
1188
void shoot(playerent *p, vec &targ)
 
1189
{
 
1190
    if(p->state==CS_DEAD || p->weaponchanging) return;
 
1191
    weapon *weap = p->weaponsel;
 
1192
    if(weap)
 
1193
    {
 
1194
        weap->attack(targ);
 
1195
        loopi(NUMGUNS)
 
1196
        {
 
1197
            weapon *bweap = player1->weapons[i];
 
1198
            if(bweap != weap && bweap->busy()) bweap->attack(targ);
 
1199
        }
 
1200
    }
 
1201
}
 
1202
 
 
1203
void checkakimbo()
 
1204
{
 
1205
    if(player1->akimbo)
 
1206
    {
 
1207
        akimbo &a = *((akimbo *)player1->weapons[GUN_AKIMBO]);
 
1208
        if(a.timerout())
 
1209
        {
 
1210
            weapon &p = *player1->weapons[GUN_PISTOL];
 
1211
            player1->akimbo = false;
 
1212
            a.reset();
 
1213
            // transfer ammo to pistol
 
1214
            p.mag = min((int)p.info.magsize, max(a.mag, p.mag));
 
1215
            p.ammo = max(p.ammo, p.ammo);
 
1216
                        // fix akimbo magcontent
 
1217
                        a.mag = 0;
 
1218
                        a.ammo = 0;
 
1219
            if(player1->weaponsel->type==GUN_AKIMBO) player1->weaponswitch(&p);
 
1220
                playsoundc(S_AKIMBOOUT);
 
1221
        }
 
1222
    }
 
1223
}