From 5356eba11c4fbe85fa5e2d4b3f2810a78923e5f3 Mon Sep 17 00:00:00 2001 From: Zeno Rogue Date: Sat, 30 Mar 2019 17:51:37 +0100 Subject: [PATCH] Asteroids --- content.cpp | 4 ++ game.cpp | 3 +- geometry.cpp | 2 + graph.cpp | 5 ++ hyper.h | 7 +- hyperpoint.cpp | 13 +++- landgen.cpp | 3 + landlock.cpp | 5 +- monstergen.cpp | 21 ++++++ polygons.cpp | 13 +++- shmup.cpp | 170 +++++++++++++++++++++++++++++++++++++++++-------- system.cpp | 4 ++ 12 files changed, 216 insertions(+), 34 deletions(-) diff --git a/content.cpp b/content.cpp index 18f41251..62ec25c8 100644 --- a/content.cpp +++ b/content.cpp @@ -1257,6 +1257,10 @@ LAND( 0x30FF30, "Irradiated Field", laVariant, ZERO, itVarTreasure, RESERVED, // add new content here +LAND( 0x202020, "Asteroids", laAsteroids, ZERO, itAsteroid, RESERVED, NODESCYET) +ITEM( '*', 0xFFFFFF, "Ice Diamond", itAsteroid, IC_TREASURE, ZERO, RESERVED, osNone, NODESCYET) +MONSTER('A', 0x606040, "Asteroid", moAsteroid, ZERO, RESERVED, moAsteroid, NODESCYET) + //shmupspecials MONSTER( '@', 0xC0C0C0, "Rogue", moPlayer, ZERO | CF_PLAYER, RESERVED, moNone, "In the Shoot'em Up mode, you are armed with thrown Knives.") MONSTER( '*', 0xC0C0C0, "Knife", moBullet, ZERO | CF_BULLET, RESERVED, moNone, "A simple, but effective, missile, used by rogues.") diff --git a/game.cpp b/game.cpp index acbd4dc3..b22c9bfd 100644 --- a/game.cpp +++ b/game.cpp @@ -888,7 +888,7 @@ bool passable_for(eMonster m, cell *w, cell *from, flagtype extra) { if(m == moPair) return !(w && from && againstPair(from, w, m)) && passable(w, from, extra); if(m == passive_switch) return false; - if(minf[m].mgroup == moYeti || isBug(m) || isDemon(m) || m == moHerdBull || m == moMimic) { + if(minf[m].mgroup == moYeti || isBug(m) || isDemon(m) || m == moHerdBull || m == moMimic || m == moAsteroid) { if((isWitch(m) || m == moEvilGolem) && w->land != laPower && w->land != laHalloween) return false; return passable(w, from, extra); @@ -6958,6 +6958,7 @@ bool collectItem(cell *c2, bool telekinesis) { else playSound(c2, "pickup-orb"); if(items[itOrbChoice]) items[itOrbChoice] = 0, had_choice = true; int oc = orbcharges(it); + if(c2->land == laAsteroids) oc = 10; if(markOrb(itOrbIntensity)) oc = oc * 6 / 5; if(!items[it]) items[it]++; items[it] += oc; diff --git a/geometry.cpp b/geometry.cpp index b30d272e..3187007a 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -27,6 +27,8 @@ ld hexshift; ld sword_size = 0; +ld asteroid_size[8]; + // the results are: // hexf = 0.378077 hcrossf = 0.620672 tessf = 1.090550 // hexhexdist = 0.566256 diff --git a/graph.cpp b/graph.cpp index 0b547bc0..68832f08 100644 --- a/graph.cpp +++ b/graph.cpp @@ -1891,6 +1891,11 @@ bool drawMonsterType(eMonster m, cell *where, const transmatrix& V, color_t col, ShadowV(V, shTentHead, PPR::GIANTSHADOW); return false; } + + case moAsteroid: { + queuepoly(V, shAsteroid[1], darkena(col, 0, 0xFF)); + return false; + } default: ; } diff --git a/hyper.h b/hyper.h index 7e928a5b..c9b3cf21 100644 --- a/hyper.h +++ b/hyper.h @@ -233,6 +233,7 @@ constexpr transmatrix diag(ld a, ld b, ld c, ld d) { #endif } +const static hyperpoint Hypc = hyperpoint(0, 0, 0, 0); // identity matrix const static transmatrix Id = diag(1,1,1,1); @@ -898,18 +899,19 @@ namespace shmup { eMonster parenttype; // type of the parent int nextshot; // when will it be able to shot (players/flailers) int pid; // player ID - char hitpoints; + int hitpoints; // hitpoints; or time elapsed in Asteroids int stunoff; int blowoff; double swordangle; // sword angle wrt at double vel; // velocity, for flail balls double footphase; bool isVirtual; // off the screen: gmatrix is unknown, and pat equals at + hyperpoint inertia;// for frictionless lands monster() { dead = false; inBoat = false; parent = NULL; nextshot = 0; stunoff = 0; blowoff = 0; footphase = 0; no_targetting = false; - swordangle = 0; + swordangle = 0; inertia = Hypc; } void store(); @@ -1641,6 +1643,7 @@ void destroyBoats(cell *c, cell *cf, bool strandedToo); extern bool showoff; extern int lastexplore; extern int truelotus; +extern int asteroids_generated, asteroid_orbs_generated; extern eLand lastland; extern time_t timerstart; extern bool timerstopped; diff --git a/hyperpoint.cpp b/hyperpoint.cpp index 51795bdd..b79bd04b 100644 --- a/hyperpoint.cpp +++ b/hyperpoint.cpp @@ -136,9 +136,6 @@ hyperpoint hpxy3(ld x, ld y, ld z) { return hpxyz3(x,y,z, euclid ? 1 : sphere ? sqrt(1-x*x-y*y-z*z) : sqrt(1+x*x+y*y+z*z)); } -// center of the pseudosphere -const hyperpoint Hypc = hyperpoint(0,0,0,0); - // origin of the hyperbolic plane const hyperpoint C02 = hyperpoint(0,0,1,0); const hyperpoint C03 = hyperpoint(0,0,0,1); @@ -241,6 +238,16 @@ transmatrix cspin(int a, int b, ld alpha) { transmatrix spin(ld alpha) { return cspin(0, 1, alpha); } +transmatrix random_spin() { + if(DIM == 2) return spin(randd() * 2 * M_PI); + else { + ld alpha2 = acos(randd() * 2 - 1); + ld alpha = randd() * 2 * M_PI; + ld alpha3 = randd() * 2 * M_PI; + return cspin(0, 1, alpha) * cspin(0, 2, alpha2) * cspin(1, 2, alpha3); + } + } + transmatrix eupush(ld x, ld y) { transmatrix T = Id; T[0][DIM] = x; diff --git a/landgen.cpp b/landgen.cpp index 53eb6422..05db6518 100644 --- a/landgen.cpp +++ b/landgen.cpp @@ -2377,6 +2377,9 @@ void giantLandSwitch(cell *c, int d, cell *from) { c->monst = moGhost; break; + case laAsteroids: + break; + case landtypes: break; } } diff --git a/landlock.cpp b/landlock.cpp index decc186b..11865230 100644 --- a/landlock.cpp +++ b/landlock.cpp @@ -237,6 +237,9 @@ int isNative(eLand l, eMonster m) { case laMagnetic: return isMagneticPole(m) ? 2 : 0; + case laAsteroids: + return m == moAsteroid ? 2 : 0; + case landtypes: return 0; } return false; @@ -316,7 +319,7 @@ bool landUnlocked(eLand l) { case laStorms: case laWhirlwind: return gold() >= R60; - case laWildWest: case laHalloween: + case laWildWest: case laHalloween: case laAsteroids: return false; case laIce: case laJungle: case laCaves: case laDesert: diff --git a/monstergen.cpp b/monstergen.cpp index 7465f4ee..77485a9c 100644 --- a/monstergen.cpp +++ b/monstergen.cpp @@ -291,6 +291,7 @@ eItem wanderingTreasure(cell *c) { if(l == laEAir) return itAirShard; if(l == laEEarth) return itEarthShard; if(l == laElementalWall) return itNone; + if(l == laAsteroids) return itNone; if(l == laMirror && c->type != 6) return itNone; if(l == laMirrorOld && c->type != 6) return itNone; if(l == laEmerald) { @@ -498,6 +499,26 @@ void wandering() { else if(c->monst || c->pathdist == PINFD) break; + else if(c->land == laAsteroids) { + int gen = 0; + if(asteroids_generated * 12 <= items[itAsteroid]) gen = 2; + if(gen == 0) { + int qty = 0; + for(cell *c: currentmap->allcells()) if(c->monst == moAsteroid) qty++; + if(!qty) gen = 1; + } + if(gen) c->monst = moAsteroid, c->hitpoints = 4; + if(gen == 2) + asteroids_generated++; + + if(items[itAsteroid] > (asteroid_orbs_generated+2) * (asteroid_orbs_generated+3) && !c->item) { + c->item = pick(itOrbThorns, itOrbSide1, itOrbSide2, itOrbSide3, itOrbShield, itOrbLife); + asteroid_orbs_generated++; + } + + break; + } + else if(c->land == laClearing && wchance(items[itMutant2], 150) && items[itMutant2] >= 15 && !c->monst && c->type == 7) c->monst = moRedFox; diff --git a/polygons.cpp b/polygons.cpp index d473be59..e1f14308 100644 --- a/polygons.cpp +++ b/polygons.cpp @@ -1677,7 +1677,7 @@ hpcshape shDragonWings, shSolidBranch, shWeakBranch, shBead0, shBead1, shBatWings, shBatBody, shBatMouth, shBatFang, shBatEye, - shParticle[16], + shParticle[16], shAsteroid[8], shReptile[5][4], shReptileBody, shReptileHead, shReptileFrontFoot, shReptileRearFoot, shReptileFrontLeg, shReptileRearLeg, shReptileTail, shReptileEye, @@ -2311,7 +2311,16 @@ void procedural_shapes() { for(int i=0; i<16; i++) { bshape(shParticle[i], PPR::PARTICLE); for(int t=0; t<6; t++) - hpcpush(xspinpush0(M_PI * t * 2 / 6 + M_PI * 2/6 * hrand(100) / 150., (0.03 + hrand(100) * 0.0003) * scalefactor)); +// hpcpush(xspinpush0(M_PI * t * 2 / 6 + M_PI * 2/6 * hrand(100) / 150., (0.03 + hrand(100) * 0.0003) * scalefactor)); + hpcpush(xspinpush0(M_PI * t * 2 / 6 + M_PI * 2/6 * randd() / 1.5, (0.03 + randd() * 0.03) * scalefactor)); + hpc.push_back(hpc[last->s]); + } + + for(int i=0; i<8; i++) { + asteroid_size[i] = scalefactor * 0.1 * pow(2, (i-1) * 1. / DIM); + bshape(shAsteroid[i], PPR::PARTICLE); + for(int t=0; t<12; t++) + hpcpush(xspinpush0(M_PI * t / 6, asteroid_size[i] * (1 - randd() * .2))); hpc.push_back(hpc[last->s]); } diff --git a/shmup.cpp b/shmup.cpp index 668c1b1e..c2a239af 100644 --- a/shmup.cpp +++ b/shmup.cpp @@ -1283,6 +1283,27 @@ void visibleFor(int t) { visibleAt = max(visibleAt, curtime + t); } +ld bullet_velocity(eMonster t) { + switch(t) { + case moBullet: + return 1/300.; + case moFireball: + return 1/500.; + case moCrushball: + return 1/1000.; + case moAirball: + return 1/200.; + case moArrowTrap: + return 1/200.; + case moTongue: + return 1/1500.; + default: + return 1/300.; + } + } + +int frontdir() { return DIM == 2 ? 0 : 2; } + void shootBullet(monster *m) { monster* bullet = new monster; bullet->base = m->base; @@ -1291,6 +1312,10 @@ void shootBullet(monster *m) { bullet->parent = m; bullet->pid = m->pid; bullet->parenttype = m->type; + bullet->inertia = m->inertia; + bullet->inertia[frontdir()] += bullet_velocity(m->type) * SCALE; + bullet->hitpoints = 0; + additional.push_back(bullet); eItem orbdir[8] = { @@ -1305,6 +1330,10 @@ void shootBullet(monster *m) { bullet->parent = m; bullet->pid = m->pid; bullet->parenttype = m->type; + bullet->hitpoints = 0; + using namespace hyperpoint_vec; + bullet->inertia = spin(-M_PI/4 * i) * m->inertia; + bullet->inertia[frontdir()] += bullet_velocity(m->type) * SCALE; additional.push_back(bullet); } } @@ -1592,6 +1621,8 @@ static const int reflectflag = P_MIRRORWALL; void movePlayer(monster *m, int delta) { + bool inertia_based = m->base->land == laAsteroids; + cpid = m->pid; #if CAP_RACING @@ -1720,6 +1751,7 @@ void movePlayer(monster *m, int delta) { if(playerturn[cpid] && canmove && !blown && DIM == 2) { m->swordangle -= playerturn[cpid]; nat = nat * spin(playerturn[cpid]); + if(inertia_based) m->inertia = spin(-playerturn[cpid]) * m->inertia; } transmatrix nat0 = nat; @@ -1752,6 +1784,7 @@ void movePlayer(monster *m, int delta) { if(mdy > 1) mdy = 1; if(mdy < -1) mdy = -1; if(mdx > 1) mdx = 1; + if(mdx < -1) mdx = -1; if(racing::on) { if(m->vel * -mdy < 0) mdy *= 3; @@ -1795,6 +1828,14 @@ void movePlayer(monster *m, int delta) { nextstep: transmatrix nat1 = nat; + + hyperpoint avg_inertia; + + if(inertia_based) { + avg_inertia = m->inertia; + m->inertia[frontdir()] += playergo[cpid] / 1000; + avg_inertia[frontdir()] += playergo[cpid] / 2000; + } for(int igo=0; igoinertia[0] = playerstrafe[cpid] / delta; + m->inertia[1] = 0; + m->inertia[2] = playergo[cpid] / delta; + } + else if(playergo[cpid]) { nat = nat1 * spin(igospan[igo]) * xpush(playergo[cpid]) * spin(-igospan[igo]); + m->inertia = spin(igospan[igo]) * xpush0(playergo[cpid] / delta); + } // spin(span[igo]) * xpush(playergo[cpid]) * spin(-span[igo]); @@ -1898,13 +1950,16 @@ void movePlayer(monster *m, int delta) { if(!go) { playergo[cpid] = playergoturn[cpid] = playerstrafe[cpid] = 0; if(DIM == 3) playerturn[cpid] = playerturny[cpid] = 0; + m->inertia = Hypc; } if(go) { - if(DIM == 3) + if(DIM == 3) { swordmatrix[cpid] = cspin(1, 2, -playerturny[cpid]) * cspin(0, 2, -playerturn[cpid]) * swordmatrix[cpid]; + m->inertia = cspin(1, 2, -playerturny[cpid]) * cspin(0, 2, -playerturn[cpid]) * m->inertia; + } if(c2 != m->base) { if(cellUnstable(m->base) && !markOrb(itOrbAether)) @@ -2320,7 +2375,37 @@ transmatrix frontpush(ld x) { if(DIM == 2) return xpush(x); else return cpush(2, x); } + +ld collision_distance(monster *bullet, monster *target) { + if(target->type == moAsteroid) + return SCALE * 0.15 + asteroid_size[target->hitpoints & 7]; + return SCALE * 0.3; + } + +void spawn_asteroids(monster *bullet, monster *target) { + if(target->hitpoints <= 1) return; + hyperpoint rnd = random_spin() * point2(SCALE/3000., 0); + hyperpoint bullet_inertia = inverse(target->pat) * bullet->pat * bullet->inertia; + + for(int i=0; i<2; i++) { + using namespace hyperpoint_vec; + monster* child = new monster; + child->base = target->base; + child->at = target->at; + child->type = target->type; + child->parent = NULL; + child->pid = target->pid; + child->parenttype = target->type; + child->inertia = target->inertia; + child->inertia += bullet_inertia / 5; + child->hitpoints = target->hitpoints - 1; + if(i == 0) child->inertia += rnd; + if(i == 1) child->inertia -= rnd; + additional.push_back(child); + } + } + void moveBullet(monster *m, int delta) { cpid = m->pid; m->findpat(); @@ -2329,6 +2414,12 @@ void moveBullet(monster *m, int delta) { transmatrix nat0 = m->pat; transmatrix nat = m->pat; + bool inertia_based = m->base->land == laAsteroids; + + if(m->base->land == laAsteroids) { + m->hitpoints += delta; + if(m->hitpoints >= 500) m->dead = true; + } if(isReptile(m->base->wall)) m->base->wparam = reptilemax(); @@ -2339,22 +2430,17 @@ void moveBullet(monster *m, int delta) { nat = nat * rspintox(inverse(m->pat) * m->parent->pat * C0) * spin(M_PI); } } - else if(m->type == moBullet) - m->vel = 1/300.; - else if(m->type == moFireball) - m->vel = 1/500.; - else if(m->type == moCrushball) - m->vel = 1/1000.; - else if(m->type == moAirball) - m->vel = 1/200.; - else if(m->type == moArrowTrap) - m->vel = 1/200.; - else if(m->type == moTongue) { - m->vel = 1/1500.; - if(m->isVirtual || !m->parent || intval(nat*C0, m->parent->pat*C0) > SCALE2 * 0.4) - m->dead = true; + else m->vel = bullet_velocity(m->type); + + if(m->type == moTongue && (m->isVirtual || !m->parent || intval(nat*C0, m->parent->pat*C0) > SCALE2 * 0.4)) + m->dead = true; + + if(inertia_based) { + ld r = hypot_d(DIM, m->inertia); + nat = nat * rspintox(m->inertia) * xpush(r * delta) * spintox(m->inertia); } - nat = nat * frontpush(delta * SCALE * m->vel / speedfactor()); + else + nat = nat * frontpush(delta * SCALE * m->vel / speedfactor()); cell *c2 = m->findbase(nat); if(m->parent && isPlayer(m->parent) && markOrb(itOrbLava) && c2 != m->base && !isPlayerOn(m->base)) @@ -2410,6 +2496,8 @@ void moveBullet(monster *m, int delta) { if(!m->isVirtual) for(monster* m2: nonvirtual) { if(m2 == m || (m2 == m->parent && m->vel >= 0) || m2->parent == m->parent) continue; + + if(m2->dead) continue; eMonster ptype = parentOrSelf(m)->type; bool slayer = m->type == moCrushball || @@ -2424,9 +2512,9 @@ void moveBullet(monster *m, int delta) { // fireballs/airballs don't collide if(m->type == moFireball && m2->type == moFireball) continue; if(m->type == moAirball && m2->type == moAirball) continue; - double d = intval(m2->pat*C0, m->pat*C0); + double d = hdist(m2->pat*C0, m->pat*C0); - if(d < SCALE2 * 0.1) { + if(d < collision_distance(m, m2)) { if(m2->type == passive_switch) { m->dead = true; continue; } @@ -2526,6 +2614,10 @@ void moveBullet(monster *m, int delta) { multi::kills[cpid]--; mirrorspirits++; } + if(m2->dead && m2->type == moAsteroid) { + gainItem(itAsteroid); + spawn_asteroids(m, m2); + } } } } @@ -2553,6 +2645,8 @@ bool dragonbreath(cell *dragon) { void moveMonster(monster *m, int delta) { + bool inertia_based = m->type == moAsteroid; + bool stunned = m->stunoff > curtime || m->blowoff > curtime; if(stunned && cellUnstable(m->base)) @@ -2623,7 +2717,14 @@ void moveMonster(monster *m, int delta) { if(nearplayer) markOrb(itOrbBeauty), step /= 2; } - if(m->isVirtual) return; + if(m->isVirtual) { + if(m->type == moAsteroid) { + ld r = hypot_d(DIM, m->inertia); + transmatrix nat = m->pat * rspintox(m->inertia) * xpush(r * delta) * spintox(m->inertia); + m->rebasePat(nat); + } + return; + } transmatrix nat = m->pat; if(stunned) { @@ -2643,6 +2744,8 @@ void moveMonster(monster *m, int delta) { } else if(m->type == moRagingBull && m->stunoff == CHARGING) ; + + else if(m->type == moAsteroid) ; else { @@ -2809,7 +2912,12 @@ void moveMonster(monster *m, int delta) { return; } - if(DIM == 3 && igo) { + if(inertia_based) { + if(igo) return; + ld r = hypot_d(DIM, m->inertia); + nat = nat * rspintox(m->inertia) * xpush(r * delta) * spintox(m->inertia); + } + else if(DIM == 3 && igo) { ld fspin = rand() % 1000; nat = nat0 * cspin(1,2,fspin) * spin(igospan[igo]) * xpush(step) * spin(-igospan[igo]) * cspin(2,1,fspin); } @@ -2818,7 +2926,7 @@ void moveMonster(monster *m, int delta) { } if(m->type != moRagingBull && !peace::on) - if(intval(nat*C0, goal*C0) >= intval(m->pat*C0, goal*C0) && !stunned && !carried) { + if(intval(nat*C0, goal*C0) >= intval(m->pat*C0, goal*C0) && !stunned && !carried && !inertia_based) { igo++; goto igo_retry; } for(int i=0; iisVirtual) for(monster *m2: nonvirtual) if(m2!=m && m2->type != moBullet && m2->type != moArrowTrap) { + if(!m->isVirtual && m->type != moAsteroid) for(monster *m2: nonvirtual) if(m2!=m && m2->type != moBullet && m2->type != moArrowTrap) { double d = intval(m2->pat*C0, nat*C0); if(d < SCALE2 * 0.1) crashintomon = m2; } + if(m->type == moAsteroid) for(int i=0; ipat), tC0(m->pat)) < collision_distance(pc[i], m)) + crashintomon = pc[i]; + if(!peace::on) for(int i=0; itype != moAsteroid) { igo++; goto igo_retry; } cell *c2 = m->findbase(nat); if(reflectflag & P_MIRRORWALL) reflect(c2, m->base, nat); @@ -3129,6 +3240,9 @@ void activateMonstersAt(cell *c) { enemy->hitpoints = c->hitpoints; if(c->wall == waBoat && isLeader(c->monst)) enemy->inBoat = true, c->wall = waSea; + if(c->monst == moAsteroid) { + enemy->inertia = random_spin() * point2(SCALE/3000., 0); + } c->monst = moNone; active.push_back(enemy); } @@ -3572,6 +3686,12 @@ bool drawMonster(const transmatrix& V, cell *c, const transmatrix*& Vboat, trans transmatrix t = view * spin(curtime / 50.0); queuepoly(mmscale(t, 1.15), shFlailMissile, (minf[m->type].color << 8) | 0xFF); ShadowV(view, shFlailMissile); + break; + } + case moAsteroid: { + transmatrix t = view * spin(curtime / 500.0); + queuepoly(mmscale(t, 1.15), shAsteroid[m->hitpoints & 7], (minf[m->type].color << 8) | 0xFF); + ShadowV(t, shAsteroid[m->hitpoints & 7]); break; } diff --git a/system.cpp b/system.cpp index 92c12409..aa32da59 100644 --- a/system.cpp +++ b/system.cpp @@ -17,6 +17,8 @@ bool canvas_invisible; int truelotus; int gamecount; +int asteroids_generated, asteroid_orbs_generated; + time_t timerstart, savetime; bool timerstopped; int savecount; @@ -287,6 +289,8 @@ void initgame() { gen_wandering = true; } truelotus = 0; + asteroids_generated = 0; + asteroid_orbs_generated = 0; survivalist = true; #if CAP_CRYSTAL crystal::used_compass_inside = false;