diff --git a/bigstuff.cpp b/bigstuff.cpp index 590a1a07..d6535802 100644 --- a/bigstuff.cpp +++ b/bigstuff.cpp @@ -121,7 +121,7 @@ bool grailWasFound(cell *c) { void generateAlts(heptagon *h, int levs, bool link_cdata) { if(!h->alt) return; preventbarriers(h->c7); - for(int i=0; ic7->move(i)); + forCellEx(c2, h->c7) preventbarriers(c2); if(GOLDBERG) for(int i=0; ic7); for(int i=0; iland == laOceanWall || c->land == laCaribbean || c->land == laWhirlpool || - c->land == laLivefjord || c->land == laWarpSea || c->land == laKraken || c->land == laDocks) + c->land == laLivefjord || c->land == laWarpSea || c->land == laKraken || c->land == laDocks || c->land == laBrownian) return 30; if(c->land != laOcean && !isGravityLand(c->land) && c->land != laHaunted) { return 0; @@ -1185,6 +1185,12 @@ void buildCamelotWall(cell *c) { } } +bool no_barriers_in_radius(cell *c, int rad) { + celllister cl(c, 2, 1000000, NULL); + for(cell *c: cl.lst) if(c->bardir != NODIR) return false; + return true; + } + void buildCamelot(cell *c) { int d = celldistAltRelative(c); if(tactic::on || (d <= 14 && roundTableRadius(c) > 20)) { @@ -1273,6 +1279,9 @@ void moreBigStuff(cell *c) { if(c->land == laCanvas && !eubinary && c->master->alt && !quotient) generateAlts(c->master); + if(c->land == laOcean && !generatingEquidistant && hrand(10000) < 10 && no_barriers_in_radius(c, 2)) + brownian::init(c); + if(c->land == laStorms) if(!eubinary && !quotient && !sphere) { if(c->master->alt && c->master->alt->distance <= 2) { @@ -1348,7 +1357,7 @@ void moreBigStuff(cell *c) { } } - if(c->land == laOcean || c->land == laWhirlpool) if(!(binarytiling && specialland != laWhirlpool)) { + if(among(c->land, laOcean, laWhirlpool, laBrownian)) if(!(binarytiling && specialland != laWhirlpool)) { bool fullwhirlpool = false; if(tactic::on && specialland == laWhirlpool) fullwhirlpool = true; diff --git a/classes.cpp b/classes.cpp index 674ded70..55159d39 100644 --- a/classes.cpp +++ b/classes.cpp @@ -778,6 +778,8 @@ monstertype minf[motypes] = { "for a long time. This attack can destroy other Raiders if it hits them."}, { '@', 0xC00000, "Red Jelly", jellydesc}, { '@', 0x0000C0, "Blue Jelly", jellydesc}, + { 'B', 0xE07000, "Brown Bug", NODESCYET}, + { 'B', 0xE07060, "Acid Bird", NODESCYET}, // shmup specials { '@', 0xC0C0C0, "Rogue", "In the Shoot'em Up mode, you are armed with thrown Knives."}, @@ -1238,6 +1240,9 @@ itemtype iinf[ittypes] = { { 'o', 0x202020, "Orb of Slaying", "This Orb lets you defeat Raiders and other tough single-cell monsters in melee." }, + { '*', 0x20C0C0, "Brownie", + "Tasty cookie." + }, // { '*', 0x26619C, "Lapis Lazuli", NODESCYET}, }; diff --git a/classes.h b/classes.h index c43c04f1..26a6e690 100644 --- a/classes.h +++ b/classes.h @@ -10,7 +10,7 @@ static inline void set_flag(flagtype& f, flagtype which, bool b) { else f &= ~which; } -static const int motypes = 162; +static const int motypes = 164; struct monstertype { char glyph; @@ -67,6 +67,7 @@ enum eMonster { moNorthPole, moSouthPole, moPair, moHexDemon, moAltDemon, moMonk, moCrusher, moSwitch1, moSwitch2, + moBrownBug, moAcidBird, // shmup specials moPlayer, moBullet, moFlailBullet, moFireball, moTongue, moAirball, moCrushball, // temporary @@ -83,7 +84,7 @@ struct genderswitch_t { #define NUM_GS 6 -static const int ittypes = 130; +static const int ittypes = 131; struct itemtype { char glyph; @@ -129,7 +130,8 @@ enum eItem { itOrbSide1, itOrbSide2, itOrbSide3, itOrbLava, itOrbMorph, itGlowCrystal, itSnake, itDock, itRuins, itMagnet, itSwitch, - itOrbPhasing, itOrbMagnetism, itOrbSlaying + itOrbPhasing, itOrbMagnetism, itOrbSlaying, + itBrownian }; static const int walltypes = 109; diff --git a/compileunits.h b/compileunits.h index 030def83..1007f0a0 100644 --- a/compileunits.h +++ b/compileunits.h @@ -41,6 +41,9 @@ #include "flags.cpp" #include "yendor.cpp" #include "complex.cpp" +#if CAP_COMPLEX2 +#include "complex2.cpp" +#endif #include "savemem.cpp" #include "game.cpp" #include "orbgen.cpp" diff --git a/complex2.cpp b/complex2.cpp new file mode 100644 index 00000000..def3b702 --- /dev/null +++ b/complex2.cpp @@ -0,0 +1,122 @@ +// Hyperbolic Rogue + +// namespaces for complex features (whirlwind, whirlpool, elec, princess, clearing, +// mirror, hive, heat + livecaves, etc.) + +// Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details + +#ifdef CAP_COMPLEX2 + +namespace hr { + +namespace brownian { + + map> > futures; + int centersteps = 0; + int totalsteps = 0; + + void rise(cell *c, int val) { + if(c->wall == waSea) c->wall = waNone; + if(c->land == laOcean || c->land == laNone) { + c->land = laBrownian; + c->landparam = 0; + } + c->bardir = NOBARRIERS; + forCellCM(c1, c) { + c1->bardir = NOBARRIERS; + if(c1->mpdist > BARLEV) { + setdist(c1, BARLEV, c); + } + if(c1->land == laOcean) { + c1->land = laBrownian; + c1->landparam = 0; + } + } + c->landparam += val; + } + + void recurse(cell *c, bool fat) { + while(true) { + totalsteps++; + if(celldist(c) >= (fat ? 30 : 20) + celldist(cwt.at)) { + cell *c1 = c; + while(true) { + cell *c2 = ts::left_parent(c1, celldist); + if(!c2 || c2->mpdist < BARLEV) break; + setdist(c2, BARLEV, c1); + if(c2->land == laOcean) { + c2->land = laBrownian; + c2->landparam = 0; + } + c1 = c2; + } + futures[c1].emplace_back(c, fat); + return; + } + if(c->mpdist <= 7) { centersteps++; return; } + // while(hrand(1000) < 1000 * chance) recurse(c); + if(fat) recurse(c, false); + if(!fat && hrand(100000) == 0) recurse(c, true); + rise(c, fat ? 256 : 1); + c = c->cmove(hrand(c->type)); + } + } + + void dissolve_brownian(cell *c, int x) { + if(c->land == laBrownian) { + if(among(c->wall, waNone, waStrandedBoat, waMineOpen, waFire)) { + if(c->landparam >= 4 * level) c->landparam = 4 * level - 1; + c->landparam -= level * x; + c->wall = waNone; + if(c->landparam < 0) c->wall = waSea, c->landparam = 0; + if(c->landparam == 0) c->landparam = 1; + } + } + } + + void dissolve(cell *c, int x) { + destroyTrapsAround(c); + if(c->land == laBrownian) + dissolve_brownian(c, x); + else if(c->wall == waRed2) c->wall = waRed1; + else if(c->wall == waRed3) c->wall = waRed2; + else if(among(c->wall, waRed1, waDeadfloor2, waRubble, waBoat, waFire, waCIsland, waCIsland2, waBigBush, waSmallBush)) c->wall = waNone; + else if(c->wall == waStrandedBoat) c->wall = waNone; + else if(c->wall == waFrozenLake) c->wall = waLake; + else if(among(c->wall, waReptile, waGargoyleFloor) || cellUnstable(c)) c->wall = waChasm; + else if(among(c->wall, waNone, waDock, waBurningDock, waFloorA, waFloorB, waCavefloor, waDeadfloor, waMineMine, waMineUnknown, waMineOpen, waOpenGate, waClosePlate, waOpenPlate, waGargoyleBridge, waReptileBridge)) + c->wall = waSea; + else if(cellHalfvine(c)) destroyHalfvine(c, waNone, 4); + } + + void init(cell *c) { + recurse(cwt.at, true); + recurse(cwt.at, true); + } + + void build(cell *c, int d) { + if(futures.count(c)) { + for(pair p: futures[c]) + recurse(p.first, p.second); + futures.erase(c); + printf("centersteps = %d futures = %d totalsteps = %d\n", centersteps, isize(futures), totalsteps); + } + + ONEMPTY { + if(hrand(8000) < 50 && c->landparam >= 4 && c->landparam < 24) + c->item = itBrownian; + if(hrand(8000) < 30) + c->monst = moAcidBird; + else if(hrand(8000) < 30) + c->monst = moAlbatross; + else if(hrand(8000) < 30) { + c->monst = moBrownBug; + c->hitpoints = 3; + } + } + } + + } + +} +#endif diff --git a/flags.cpp b/flags.cpp index 706c3084..3b753f43 100644 --- a/flags.cpp +++ b/flags.cpp @@ -127,10 +127,9 @@ bool isMetalBeast(eMonster m) { } bool isStunnable(eMonster m) { - return m == moPalace || m == moFatGuard || m == moSkeleton || isPrincess(m) || - isMetalBeast(m) || m == moTortoise || isDragon(m) || - m == moReptile || m == moTerraWarrior || m == moSalamander || - m == moVizier; + return + isMetalBeast(m) || isDragon(m) || isPrincess(m) || + among(m, moPalace, moFatGuard, moSkeleton, moTortoise, moReptile, moTerraWarrior, moSalamander, moVizier, moBrownBug); } bool hasHitpoints(eMonster m) { @@ -338,7 +337,12 @@ int snakelevel(eWall w) { return 0; } -int snakelevel(cell *c) { return snakelevel(c->wall); } +int snakelevel(cell *c) { +#if CAP_COMPLEX2 + if(c->land == laBrownian && among(c->wall, waNone, waMineMine, waFire)) return min(c->landparam / brownian::level, 3); +#endif + return snakelevel(c->wall); + } bool isWall(cell *w) { if(w->wall == waNone || isAlchAny(w) || @@ -365,7 +369,8 @@ bool isWall(cell *w) { bool isAngryBird(eMonster m) { return m == moEagle || m == moAlbatross || m == moBomberbird || m == moGargoyle || m == moWindCrow || m == moSparrowhawk || - m == moVampire || m == moBat || m == moButterfly || m == moGadfly; + m == moVampire || m == moBat || m == moButterfly || m == moGadfly || + m == moAcidBird; } bool isBird(eMonster m) { @@ -407,6 +412,7 @@ bool normalMover(eMonster m) { m == moHunterGuard || m == moHunterChanging || m == moIceGolem || m == moSwitch1 || m == moSwitch2 || m == moCrusher || m == moPair || + m == moBrownBug || isMagneticPole(m) || slowMover(m); } diff --git a/game.cpp b/game.cpp index 9c744c71..7d30d896 100644 --- a/game.cpp +++ b/game.cpp @@ -376,6 +376,7 @@ int* killtable[] = { &kills[moSalamander], &kills[moLavaWolf], &kills[moSwitch1], &kills[moSwitch2], &kills[moMonk], &kills[moCrusher], &kills[moHexDemon], &kills[moAltDemon], &kills[moPair], + &kills[moBrownBug], &kills[moAcidBird], NULL }; @@ -1694,6 +1695,19 @@ bool earthWall(cell *c) { } bool snakepile(cell *c, eMonster m) { + if(c->land == laBrownian) { + if(c->wall == waNone) { + #if CAP_COMPLEX2 + c->landparam += brownian::level; + #endif + return true; + } + if(c->wall == waSea || c->wall == waBoat) { + c->wall = waNone; + c->landparam++; + return true; + } + } if(c->item && c->wall != waRed3) c->item = itNone; if(c->wall == waRed1 || c->wall == waOpenGate) c->wall = waRed2; else if(c->wall == waRed2) c->wall = waRed3; @@ -1821,11 +1835,12 @@ void explodeMine(cell *c) { drawFireParticles(c, 30, 150); c->wall = waMineOpen; + brownian::dissolve_brownian(c, 2); makeflame(c, 20, false); - for(int i=0; itype; i++) if(c->move(i)) { - cell *c2 = c->move(i); + forCellEx(c2, c) { destroyTrapsOn(c2); + brownian::dissolve_brownian(c2, 1); if(c2->wall == waRed2 || c2->wall == waRed3) c2->wall = waRed1; else if(c2->wall == waDeadTroll || c2->wall == waDeadTroll2 || c2->wall == waPetrified || c2->wall == waGargoyle) { @@ -1875,9 +1890,9 @@ void stunMonster(cell *c2) { c2->monst == moHedge ? 1 : c2->monst == moFlailer ? 1 : c2->monst == moSalamander ? 6 : + c2->monst == moBrownBug ? 3 : 3); - if(c2->monst != moSkeleton && !isMetalBeast(c2->monst) && c2->monst != moTortoise && - c2->monst != moReptile && c2->monst != moSalamander) { + if(!isMetalBeast(c2->monst) && !among(c2->monst, moSkeleton, moReptile, moSalamander, moTortoise, moBrownBug)) { c2->hitpoints--; if(c2->monst == moPrincess) playSound(c2, princessgender() ? "hit-princess" : "hit-prince"); @@ -2077,9 +2092,11 @@ void killMonster(cell *c, eMonster who, flagtype deathflags) { playSound(c, "splash" + pick12()); destroyHalfvine(c); minerEffect(c); + brownian::dissolve_brownian(c, 1); for(int i=0; itype; i++) if(passable(c->move(i), c, P_MONSTER | P_MIRROR | P_CLIMBUP | P_CLIMBDOWN)) { destroyHalfvine(c->move(i)); minerEffect(c->move(i)); + brownian::dissolve_brownian(c->move(i), 1); if(c->move(i)->monst == moSlime || c->move(i)->monst == moSlimeNextTurn) killMonster(c->move(i), who); } @@ -2107,6 +2124,13 @@ void killMonster(cell *c, eMonster who, flagtype deathflags) { if(m == moVineBeast) petrify(c, waVinePlant, m), pcount = 0; if(isBird(m)) moveEffect(c, c, moDeadBird, -1); + if(m == moAcidBird) { + playSound(c, "die-bomberbird"); + pcount = 64; + #if CAP_COMPLEX2 + brownian::dissolve(c, 1); + #endif + } if(m == moBomberbird || m == moTameBomberbird) { pcount = 0; playSound(c, "die-bomberbird"); @@ -2149,6 +2173,10 @@ void killMonster(cell *c, eMonster who, flagtype deathflags) { if(doesFall(c)) fallingFloorAnimation(c, waRed1, m), pcount = 0; else if(snakepile(c, m)) pcount = 0; } + if(m == moBrownBug) { + if(doesFall(c)) ; + else if(snakepile(c, m)) pcount = 0; + } if(m == moDarkTroll) { playSound(c, "die-troll"); if(doesFall(c)) fallingFloorAnimation(c, waDeadwall, m), pcount = 0; @@ -2467,6 +2495,11 @@ bool attackMonster(cell *c, flagtype flags, eMonster killer) { void pushMonster(cell *ct, cell *cf, int direction_hint) { moveMonster(ct, cf, direction_hint); + if(ct->monst == moBrownBug) { + int t = snakelevel(ct) - snakelevel(cf); + if(t > 0) + ct->stuntime = min(ct->stuntime + 2 * t, 7); + } } bool destroyHalfvine(cell *c, eWall newwall, int tval) { @@ -3579,6 +3612,8 @@ void moveMonster(cell *ct, cell *cf, int direction_hint) { if(isMetalBeast(m)) ct->stuntime += 2; if(m == moTortoise) ct->stuntime += 3; if(m == moDraugr && ct->land != laBurial && ct->land != laHalloween) ct->stuntime += 2; + if(m == moBrownBug && snakelevel(ct) < snakelevel(cf)) ct->stuntime += 2; + if(m == moBrownBug && snakelevel(ct) < snakelevel(cf) - 1) ct->stuntime += 2; } if(isWitch(m) && ct->item == itOrbLife && passable(cf, NULL, P_MIRROR)) { @@ -3811,8 +3846,11 @@ int moveval(cell *c1, cell *c2, int d, flagtype mf) { if(m == moHunterChanging && c2->pathdist > c1->pathdist) return 1600; if((mf & MF_PATHDIST) && !pathlock) printf("using MF_PATHDIST without path\n"); + + int bonus = 0; + if(m == moBrownBug && snakelevel(c2) < snakelevel(c1)) bonus = -10; - if(hunt && (mf & MF_PATHDIST) && c2->pathdist < c1->pathdist && !peace::on) return 1500; // good move + if(hunt && (mf & MF_PATHDIST) && c2->pathdist < c1->pathdist && !peace::on) return 1500 + bonus; // good move // prefer straight direction when wandering int dd = angledist(c1, c1->mondir, d); @@ -5181,9 +5219,7 @@ int movevalue(eMonster m, cell *c, cell *c2, flagtype flags) { val = (m == moPrincessArmed && isPrincess(c2->monst)) ? 14000 : // jealousy! isActiveEnemy(c2,m) ? 12000 : - (c2->monst == moSkeleton || c2->monst == moMetalBeast || - c2->monst == moReptile || c2->monst == moTortoise || - c2->monst == moSalamander || c2->monst == moTerraWarrior) ? -400 : + among(c2->monst, moSkeleton, moMetalBeast, moReptile, moTortoise, moSalamander, moTerraWarrior, moBrownBug) ? -400 : isIvy(c2) ? 8000 : isInactiveEnemy(c2,m) ? 1000 : -500; diff --git a/graph.cpp b/graph.cpp index 30239fa2..363ad09a 100644 --- a/graph.cpp +++ b/graph.cpp @@ -1252,7 +1252,7 @@ bool drawMonsterType(eMonster m, cell *where, const transmatrix& V, color_t col, queuepoly(VFISH, shShark, darkena(col, 0, 0xFF)); else if(m == moEagle || m == moParrot || m == moBomberbird || m == moAlbatross || m == moTameBomberbird || m == moWindCrow || m == moTameBomberbirdMoved || - m == moSandBird) { + m == moSandBird || m == moAcidBird) { ShadowV(V, shEagle); queuepoly(VBIRD, shEagle, darkena(col, 0, 0xFF)); } @@ -1695,7 +1695,7 @@ bool drawMonsterType(eMonster m, cell *where, const transmatrix& V, color_t col, queuepolyat(VBODY * spin(M_PI), shTentacle, 0x000000C0, PPR::TENTACLE1); queuepolyat(VBODY, shDisk, darkena(col, 0, 0xFF), PPR::MONSTER_BODY); } - else if(isMetalBeast(m)) { + else if(isMetalBeast(m) || m == moBrownBug) { ShadowV(V, shTrylobite); if(!mmspatial) queuepoly(VABODY, shTrylobite, darkena(col, 1, 0xC0)); @@ -2852,6 +2852,7 @@ void setcolors(cell *c, color_t& wcol, color_t& fcol) { // gradient(0xFF8000, 0xFFF000, 2*level, c->landparam, 3*level-1) : 0xC00000; break; + } #endif case laVolcano: { @@ -3503,6 +3504,7 @@ int getfd(cell *c) { case laPalace: case laCA: case laDual: + case laBrownian: return 1; case laTrollheim: @@ -4307,6 +4309,13 @@ void drawcell(cell *c, transmatrix V, int spinv, bool mirrored) { case laIvoryTower: case laDungeon: set_towerfloor(c); break; + + case laBrownian: + if(among(c->wall, waSea, waBoat)) + set_floor(shCloudFloor); + else + set_floor(shFloor); + break; default: set_floor(shFloor); @@ -4524,7 +4533,11 @@ void drawcell(cell *c, transmatrix V, int spinv, bool mirrored) { case waFrozenLake: case waLake: case waCamelotMoat: case waSea: case waOpenGate: case waBubble: case waDock: - case waNone: case waSulphur: case waMercury: + case waSulphur: case waMercury: + break; + + case waNone: + if(c->land == laBrownian) goto wa_default; break; case waRose: { diff --git a/hyper.h b/hyper.h index ab352426..bdf05037 100644 --- a/hyper.h +++ b/hyper.h @@ -4601,7 +4601,7 @@ struct exp_parser { }; -#ifdef CAP_COMPLEX2 +#if CAP_COMPLEX2 namespace brownian { const int level = 5; void init(cell *c); diff --git a/landgen.cpp b/landgen.cpp index fd931a13..a1d6c11d 100644 --- a/landgen.cpp +++ b/landgen.cpp @@ -101,37 +101,10 @@ eMonster genRuinMonster(cell *c) { return m; } -void start_brownian(cell *c, int d) { - while(true) { - if(c->wall == waBrownian) c->wall = waNone; - c->landparam++; - // snakepile(c, moRedTroll); - // c->wall = waSea; - c->bardir = NOBARRIERS; - if(d == 0) { - if(c->mpdist >= BARLEV) { - c->wall = waBrownian; - return; - } - start_brownian(c, 1); - d = 1; - continue; - } - int q = 0; - cell *good = NULL; - forCellCM(c2, c) - if(c2->mpdist > 7 && (c2->land == laBrownian || c2->land == laNone)) { - q++; - if(q==1 || hrand(q) == 0) good = c2; - } - if(!q) break; - d++; if(d == 7) d = 0; - c = good; - } - } - // the giant switch generating most of the lands... +void gen_brownian(cell *c); + void giantLandSwitch(cell *c, int d, cell *from) { switch(c->land) { @@ -2183,8 +2156,7 @@ void giantLandSwitch(cell *c, int d, cell *from) { break; case laBrownian: - if(c->wall == waBrownian || (d == 9 && hrand(10000) < 5)) - start_brownian(c, 0); + brownian::build(c, d); break; case laMirrored: @@ -2405,6 +2377,7 @@ void setdist(cell *c, int d, cell *from) { if(c->land == laClearing && !tactic::on) setland(c, laOvergrown); if(c->land == laWhirlpool && !tactic::on && !yendor::on) setland(c, laOcean); if(c->land == laCamelot && !tactic::on) setland(c, laCrossroads); + if(c->land == laBrownian && !tactic::on && !chaosmode) setland(c, laOcean); #if CAP_DAILY if(!daily::on) { diff --git a/landlock.cpp b/landlock.cpp index 52c13587..b6bda74e 100644 --- a/landlock.cpp +++ b/landlock.cpp @@ -232,6 +232,8 @@ int isNative(eLand l, eMonster m) { eItem treasureType(eLand l) { switch(l) { case laBrownian: + return itBrownian; + case laIce: return itDiamond; case laJungle: return itRuby; case laCaves: return itGold; diff --git a/sysconfig.h b/sysconfig.h index e8bd2eef..53c0a6e2 100644 --- a/sysconfig.h +++ b/sysconfig.h @@ -249,6 +249,14 @@ #define CAP_SHMUP_GOOD (!ISMOBWEB) #endif +#ifndef CAP_BONUS +#define CAP_BONUS 0 +#endif + +#ifndef CAP_COMPLEX2 +#define CAP_COMPLEX2 CAP_BONUS +#endif + #if ISMOBILE #define EXTRALICENSE "\n\nHyperRogue soundtrack by Shawn Parrotte (http://www.shawnparrotte.com), under the Creative Commons BY-SA 3.0 license, http://creativecommons.org/licenses/by-sa/3.0/" #undef XEXTRALICENSE diff --git a/system.cpp b/system.cpp index 581b2999..6a561298 100644 --- a/system.cpp +++ b/system.cpp @@ -131,6 +131,8 @@ void initgame() { cwt.at = currentmap->gamestart(); cwt.spin = 0; cwt.mirrored = false; cwt.at->land = firstland; + + if(firstland == laBrownian) brownian::init(cwt.at); chaosAchieved = false;