From a6cbedc944b0a4ab72dad30902b74e8390d0d395 Mon Sep 17 00:00:00 2001 From: Zeno Rogue Date: Sun, 27 Mar 2022 13:23:59 +0200 Subject: [PATCH] multi:: PvP, friendly-fire, and self-hits options --- achievement.cpp | 5 +++++ graph.cpp | 2 +- multi.cpp | 39 ++++++++++++++++++++++++++++++++------- shmup.cpp | 46 +++++++++++++++++++++++++++++++++++++++------- 4 files changed, 77 insertions(+), 15 deletions(-) diff --git a/achievement.cpp b/achievement.cpp index 0a6aaeb5..3b1c916a 100644 --- a/achievement.cpp +++ b/achievement.cpp @@ -135,6 +135,8 @@ EX bool wrongMode(char flags) { dls = lsChaos; if(land_structure != dls) return true; + if(numplayers() > 1 && !multi::friendly_fire) return true; + if(numplayers() > 1 && multi::pvp_mode) return true; if((numplayers() > 1) != (flags == rg::multi)) return true; return false; } @@ -777,6 +779,9 @@ EX void achievement_final(bool really_final) { if(ineligible_starting_land) return; if(geometry) return; if(NONSTDVAR) return; + + if(numplayers() > 1 && !multi::friendly_fire) return; + if(numplayers() > 1 && multi::pvp) return; // determine the correct leaderboard ID for 'total score' // or return if no leaderboard for the current mode diff --git a/graph.cpp b/graph.cpp index f7303080..972c3e3f 100644 --- a/graph.cpp +++ b/graph.cpp @@ -168,7 +168,7 @@ EX int lightat, safetyat; EX void drawLightning() { lightat = ticks; } EX void drawSafety() { safetyat = ticks; } -void drawShield(const shiftmatrix& V, eItem it) { +EX void drawShield(const shiftmatrix& V, eItem it) { #if CAP_CURVE float ds = ptick(300); color_t col = iinf[it].color; diff --git a/multi.cpp b/multi.cpp index 355687e6..cc44390a 100644 --- a/multi.cpp +++ b/multi.cpp @@ -29,6 +29,9 @@ EX namespace multi { EX charstyle scs[MAXPLAYER]; EX bool split_screen; + EX bool pvp_mode; + EX bool friendly_fire = true; + EX bool self_hits; EX int players = 1; EX cellwalker player[MAXPLAYER]; @@ -39,7 +42,7 @@ EX namespace multi { EX bool flipped[MAXPLAYER]; // treasure collection, kill, and death statistics - EX int treasures[MAXPLAYER], kills[MAXPLAYER], deaths[MAXPLAYER]; + EX int treasures[MAXPLAYER], kills[MAXPLAYER], deaths[MAXPLAYER], pkills[MAXPLAYER], suicides[MAXPLAYER]; EX bool alwaysuse = false; @@ -194,18 +197,21 @@ string listkeys(int id) { #define SCJOY 16 string dsc(int id) { - char buf[64]; - sprintf(buf, " (%d $$$, %d kills, %d deaths)", - multi::treasures[id], - multi::kills[id], - multi::deaths[id] + string buf = XLAT(" (%d $$$, %d kills, %d deaths)", + its(multi::treasures[id]), + its(multi::kills[id]), + its(multi::deaths[id]) ); + if(friendly_fire) + buf += XLAT(" (%d pkills)", its(multi::pkills[id])); + if(self_hits) + buf += XLAT(" (%d self)", its(multi::suicides[id])); return buf; } EX void resetScores() { for(int i=0; ieditable("split screen mode", 's'); + param_b(multi::pvp_mode, "pvp_mode", false) + ->editable("player vs player", 'v'); + param_b(multi::friendly_fire, "friendly_fire", true) + ->editable("friendly fire", 'f'); + param_b(multi::self_hits, "self_hits", false) + ->editable("self hits", 'h'); addsaver(alwaysuse, "use configured keys"); // unfortunately we cannot use key names here because SDL is not yet initialized for(int i=0; i<512; i++) diff --git a/shmup.cpp b/shmup.cpp index 9d6db574..4dd61836 100644 --- a/shmup.cpp +++ b/shmup.cpp @@ -50,8 +50,9 @@ struct monster { int nextshot; ///< when will it be able to shot (players/flailers) int pid; ///< player ID int hitpoints; ///< hitpoints; or time elapsed in Asteroids - int stunoff; - int blowoff; + int stunoff; ///< when does the stun end + int blowoff; ///< when does the blow end + int fragoff; ///< when does the frag end in PvP double swordangle; ///< sword angle wrt at double vel; ///< velocity, for flail balls double footphase; @@ -65,8 +66,8 @@ struct monster { monster() { dead = false; inBoat = false; parent = NULL; nextshot = 0; - stunoff = 0; blowoff = 0; footphase = 0; no_targetting = false; - swordangle = 0; inertia = Hypc; ori = Id; refs = 1; + stunoff = 0; blowoff = 0; fragoff = 0; footphase = 0; no_targetting = false; + swordangle = 0; inertia = Hypc; ori = Id; refs = 1; split_tick = -1; split_owner = -1; } @@ -270,6 +271,12 @@ void killMonster(monster* m, eMonster who_kills, flagtype flags = 0) { if(callhandlers(false, hooks_kill, m)) return; if(m->dead) return; m->dead = true; + if(isPlayer(m)) { + if(multi::cpid == m->pid) + multi::suicides[multi::cpid]++; + else if(multi::cpid >= 0) + multi::pkills[multi::cpid]++; + } if(isBullet(m) || isPlayer(m)) return; m->stk = m->base->monst; if(m->inBoat && isWatery(m->base)) { @@ -400,6 +407,9 @@ ld bullet_velocity(eMonster t) { int frontdir() { return WDIM == 2 ? 0 : 2; } +/** cannot hit yourself during first 100ms after shooting a bullet */ +const int bullet_time = 100; + void shootBullet(monster *m) { monster* bullet = new monster; bullet->base = m->base; @@ -412,6 +422,7 @@ void shootBullet(monster *m) { bullet->inertia = m->inertia; bullet->inertia[frontdir()] += bullet_velocity(m->type) * SCALE; bullet->hitpoints = 0; + bullet->fragoff = ticks + bullet_time; additional.push_back(bullet); @@ -429,6 +440,7 @@ void shootBullet(monster *m) { bullet->set_parent(m); bullet->pid = m->pid; bullet->hitpoints = 0; + bullet->fragoff = ticks + bullet_time; bullet->inertia = cspin(0, WDIM-1, -M_PI/4 * i) * m->inertia; bullet->inertia[frontdir()] += bullet_velocity(m->type) * SCALE; additional.push_back(bullet); @@ -1755,8 +1767,10 @@ void moveBullet(monster *m, int delta) { // items[itOrbWinter] = 100; items[itOrbLife] = 100; + bool no_self_hits = !multi::self_hits || m->fragoff > ticks; + if(!m->isVirtual) for(monster* m2: nonvirtual) { - if(m2 == m || (m2 == m->parent && m->vel >= 0) || m2->parent == m->parent) + if(m2 == m || (m2 == m->parent && no_self_hits) || (m2->parent == m->parent && no_self_hits)) continue; if(m2->dead) continue; @@ -1765,7 +1779,9 @@ void moveBullet(monster *m, int delta) { if(m2->type == moFlailer && m2 != m->parent) continue; // be nice to your images! would be too hard otherwise... if(isPlayerOrImage(parentOrSelf(m)->type) && isPlayerOrImage(parentOrSelf(m2)->type) && - m2->pid == m->pid) + m2->pid == m->pid && no_self_hits) + continue; + if(isPlayer(parentOrSelf(m)) && isPlayer(m2) && m2->pid != m->pid && !friendly_fire) continue; // fireballs/airballs don't collide if(m->type == moFireball && m2->type == moFireball) continue; @@ -2538,6 +2554,9 @@ EX void fixStorage() { EX hookset hooks_turn; +/** the amount of time chars are disabled in PvP */ +EX int pvp_delay = 2000; + EX void turn(int delta) { if(split_screen && subscreens::split( [delta] () { turn(delta); })) return; @@ -2753,6 +2772,15 @@ EX void turn(int delta) { items[itOrbShield] = 1; orbused[itOrbShield] = true; } + + if(pc[i]->dead && pvp_mode) { + pc[i]->dead = false; + if(ticks > pc[i]->fragoff) { + pc[i]->fragoff = ticks + pvp_delay; + pc[i]->nextshot = min(pc[i]->nextshot, pc[i]->fragoff); + multi::deaths[i]++; + } + } if(pc[i]->dead && items[itOrbLife]) { multi::deaths[i]++; @@ -3033,7 +3061,11 @@ bool celldrawer::draw_shmup_monster() { } } if(m->inBoat) m->footphase = 0; - if(mapeditor::drawplayer) drawMonsterType(moPlayer, c, view, 0xFFFFFFC0, m->footphase, 0xFFFFFFC0); + if(mapeditor::drawplayer) { + if(m->fragoff > ticks) + drawShield(view, itWarning); + drawMonsterType(moPlayer, c, view, 0xFFFFFFC0, m->footphase, 0xFFFFFFC0); + } } if(ths && h) first_cell_to_draw = false;