// Hyperbolic Rogue // namespaces for complex features (whirlwind, whirlpool, elec, princess, clearing, // mirror, hive, heat + livecaves) // Copyright (C) 2011-2016 Zeno Rogue, see 'hyper.cpp' for details #include namespace whirlwind { int fzebra3(cell *c) { if(euclid) { if(torus) return 0; eucoord x, y; decodeMaster(c->master, x, y); return 1+((((signed short)(y)+int(50000))/3)%3); } if(sphere) return getHemisphere(c, 0) > 0 ? 1 : 2; return zebra3(c); } void switchTreasure(cell *c) { c->item = itNone; if(safety) return; if(hrand(5000) < PT(100 + 2 * (kills[moAirElemental] + kills[moWindCrow]), 200) && notDippingFor(itWindstone) && getGhostcount() < 2) c->item = itWindstone; else if(hrand(5000) < 20*PRIZEMUL) placeLocalOrbs(c); } int cat(cell *c) { if(c->land != laWhirlwind) return 0; if(c->wall != waNone && c->wall != waChasm && c->wall != waSea && !isAlchAny(c) && c->wall != waMineMine && c->wall != waFire) return 0; if(c->item == itKey || c->item == itOrbYendor) return 0; if(airdist(c) < 3) return 0; if(c->monst == moHexSnake || c->monst == moHexSnakeTail) return 0; return fzebra3(c); } cell *where; int dfrom[2], dto[2], qdirs; int gdist(int d, int e) { return dirdiff(d-e, where->type); } void calcdirs(cell *c) { where = c; int d = cat(c); qdirs = 0; if(d == 0) return; int qdf = 0, qdt = 0; int cats[7]; for(int i=0; itype; i++) cats[i] = cat(createMov(c,i)); for(int i=0; itype; i++) if(cats[i] == d) { bool c1 = (cats[(i+1)%c->type] != d); bool c2 = (cats[(i+c->type-1)%c->type] != d); if(c1 && !c2) dto[qdt++] = i; if(c2 && !c1) dfrom[qdf++] = i; } qdirs = qdf; if(qdirs == 2) { int cur = gdist(dfrom[0], dto[0]) + gdist(dfrom[1], dto[1]); int alt = gdist(dfrom[0], dto[1]) + gdist(dfrom[1], dto[0]); if(alt < cur) swap(dto[0], dto[1]); } } int mindist(int d, int *tab) { if(qdirs == 0) return NODIR; if(qdirs == 1) return gdist(d, tab[0]); return min(gdist(d, tab[0]), gdist(d, tab[1])); } int winddir(int d) { if(d == -1) return 0; int mdf = mindist(d, dfrom); int mdt = mindist(d, dto); // printf("dir = %d mdf = %d mdt = %d\n", d, mdf, mdt); if(mdf < mdt) return -1; if(mdf > mdt) return 1; return 0; } void build(vector& whirlline, int d) { again: cell *at = whirlline[size(whirlline)-1]; cell *prev = whirlline[size(whirlline)-2]; for(int i=0; itype; i++) if(at->mov[i] && (euclid || at->mov[i]->master->alt) && celldistAlt(at->mov[i]) == d && at->mov[i] != prev) { whirlline.push_back(at->mov[i]); goto again; } } void moveAt(cell *c) { if(eq(c->aitmp, sval)) return; calcdirs(c); if(qdirs != 1) return; vector whirlline; whirlline.push_back(c); cell *prev = c; cell *c2 = c->mov[dfrom[0]]; while(true) { // printf("c = %p dist = %d\n", c2, c2->mpdist); if(c == c2) break; calcdirs(c2); if(qdirs == 0) break; cell *cc2 = c2; if(qdirs == 1) whirlline.push_back(c2), c2 = c2->mov[dfrom[0]]; else if(c2->mov[dto[0]] == prev) c2 = c2->mov[dfrom[1]]; else c2 = c2->mov[dfrom[0]]; prev = cc2; } int z = size(whirlline); // printf("Cycle built from %p, length = %d\n", c, z); for(int i=0; impdist, whirlline[i]->item ? '*' : ' '); whirlline[i]->aitmp = sval; if(whirlline[i]->mpdist == BARLEV) switchTreasure(whirlline[i]); } for(int i=0; iitem) animateMovement(whirlline[i+1], whirlline[i], LAYER_BOAT); } for(int i=0; iitem) collectItem(whirlline[i], true); } void move() { sval++; for(int i=0; imov[dfrom[0]]; if(!passable(c, c2, P_JUMP1)) return NULL; if(player && i == 0 && !passable(c, c2, P_ISPLAYER)) return NULL; c = c2; } calcdirs(c); if(qdirs != 1) return NULL; return c; } cell *jumpDestination(cell *c) { for(int i=0; i<2; i++) { calcdirs(c); if(qdirs != 1) return NULL; c = c->mov[dto[0]]; } calcdirs(c); if(qdirs != 1) return NULL; return c; } } namespace elec { bool havecharge, haveelec, havethunder; bool afterOrb; // extra charge from the Orb of Lightning enum eCharge { ecCharged, ecGrounded, ecIsolator, ecConductor }; bool conduct(eCharge cf, eCharge ct) { if(ct == ecIsolator) return false; if(ct == ecConductor) return true; return ct != cf; } eCharge getCharge(cell *c) { bool ao = afterOrb && c->ligon; if(c->wall == waCharged) return ecCharged; if(c->wall == waSea || c->wall == waGrounded) return ecGrounded; if(c->wall == waSandstone || c->wall == waDeadTroll || c->wall == waDeadTroll2 || c->wall == waVinePlant || c->wall == waMetal || isAlchAny(c)) return c->land == laStorms ? ecConductor : ecGrounded; if(c->wall == waBarrier) return ecIsolator; if(c->wall == waChasm) return ecIsolator; if(shmup::on ? isPlayerOn(c) : (isPlayerOn(c) || stalemate::isMoveto(c) || (items[itOrbEmpathy] && isFriendly(c)))) { if(items[itOrbShield]) return ecIsolator; if(afterOrb) return ecIsolator; if(!items[itOrbAether]) return c->land == laStorms ? ecConductor : ecGrounded; } // if(c->monst && stalemate::moveto) printf("%p: isKilled = %d\n", c, stalemate::isKilled(c)); else if( (c->monst || stalemate::isPushto(c)) && (stalemate::isPushto(c) || !stalemate::isKilled(c)) && c->monst != moGhost && c->monst != moIvyDead && c->monst != moIvyNext && !(isDragon(c->monst) && !c->hitpoints) ) return c->land == laStorms ? (ao ? ecCharged : ecConductor) : ecGrounded; if(c->land != laStorms) return ecGrounded; if(ao) return ecCharged; return ecIsolator; } // To process conductivity, consider the following graph: // - edges are between conductors and adjacent charged/grounded/conductor cells // - all charged cells are connected to one special cell '0' // - all grounded cells are connected to one special cell '1' // - cells '0' and '1' are connected // If A and B are in the same biconnected component, then there is a closed circuit, // consisting of all other cells in that component. // To find biconnected components, we are using the Hopcroft-Tarjan algorithm. struct chargedata { cell *c; int otmp; int lowlink; bool instack; bool fire; }; vector charges; vector > xstack; vector chargecells; bool hasdata(cell *c) { return c->aitmp >= 0 && c->aitmp < size(charges) && charges[c->aitmp].c == c; } void connect(int from, cell *c) { if(hasdata(c)) { // seen again: set the lowlink if(!charges[c->aitmp].instack) return; // printf("edge %d-%d\n", from, c->aitmp); if(c->aitmp < charges[from].lowlink) charges[from].lowlink = c->aitmp; } else { int id = size(charges); charges.push_back(chargedata()); chargedata& ch(charges[id]); ch.c = c; ch.otmp = c->aitmp; ch.lowlink = id; c->aitmp = id; ch.instack = true; ch.fire = false; // c->landparam = id; // printf("edge %d-%d [%s]\n", from, id, dnameof(c->wall)); xstack.push_back(make_pair(from, id)); eCharge chh = getCharge(c); if(chh == ecGrounded) { xstack.push_back(make_pair(id, 0)); ch.lowlink = 0; } else if(chh == ecCharged) { xstack.push_back(make_pair(id, 1)); if(from != 1) ch.lowlink = 1; } for(int i=0; itype; i++) { cell *c2 = c->mov[i]; if(!c2) continue; if(c2->aitmp == from) continue; eCharge ct = getCharge(c2); if(conduct(chh, ct)) connect(id, c2); } // printf("lowlink of %d [%s] = %d\n", id, dnameof(c->wall), ch.lowlink); if(ch.lowlink < charges[from].lowlink) charges[from].lowlink = ch.lowlink; if(charges[id].lowlink >= from) { while(xstack.back().first != from || xstack.back().second != id) { // printf("bcc %d,%d\n", xstack.back().first, xstack.back().second); xstack.pop_back(); } // printf("bcc %d,%d\n", xstack.back().first, xstack.back().second); xstack.pop_back(); // printf("\n"); } ch.instack = false; } } void affect(cell *c) { c->ligon = true; if(c->monst) { if(c->monst == moMetalBeast2 && !c->item) c->item = itFulgurite; killMonster(c, moLightningBolt); } if(isPlayerOn(c)) { killThePlayerAt(moLightningBolt, c, 0); } if(c->wall == waSandstone) c->wall = waNone, c->item = itFulgurite, drawParticles(c, winf[waSandstone].color, 16); if(c->wall == waDeadTroll) c->wall = waCavefloor; if(c->wall == waDeadTroll2 || isAlchAny(c) || c->wall == waVinePlant) drawParticles(c, winf[c->wall].color, 16), c->wall = waNone; /* if(c->wall == waCharged) c->wall = waMetal; */ } void listChargedCells(cell *c, eCharge last = ecConductor) { if(eq(c->aitmp, sval)) return; eCharge here = getCharge(c); /* if(c->cpdist <= 2) { printf("monst=%s ", dnameof(c->monst)); printf("wall=%s ", dnameof(c->wall)); printf("c=%p here=%d last=%d\n", c, here, last); } */ if(here == ecIsolator) c->aitmp = sval; if(!conduct(last, here)) return; if(here == ecCharged) chargecells.push_back(c); c->aitmp = sval; for(int i=0; itype; i++) { cell *c2 = c->mov[i]; if(c2) listChargedCells(c2, here); } } void init() { chargecells.clear(); if(!haveelec && !afterOrb) return; sval++; for(int i=0; iaitmp = charges[i].otmp; charges.resize(0); } void draw(cell *c, eCharge what) { if(c->ligon) return; c->ligon = true; for(int i=0; itype; i++) { cell *c2 = c->mov[i]; if(!c2) continue; eCharge ch = getCharge(c2); if(conduct(what, ch)) draw(c2, ch); } } void drawcharges() { for(int i=0; iaitmp >= 0 && c->aitmp < size(charges) && charges[c->aitmp].c == c) return charges[c->aitmp].fire; return false; } struct builder { builder() { init(); } ~builder() { cleanup(); } }; void act() { int k = tkills(); for(int i=0; iland == laStorms && !afterOrb) markOrb(itOrbShield), markOrb(itOrbAether); builder b; fire(); if(!afterOrb) achievement_count("ELEC", tkills() - k, 0); } // 0 = no close escape, 1 = close escape, 2 = message already shown int lightningfast; void checklightningfast() { if(lightningfast == 1) { addMessage(XLAT("Wow! That was close.")); lightningfast = 2; } if(lightningfast > 1) return; builder b; for(int i=0; i infos; void assign(info *i) { if(i->alt) i->alt->emeraldval = i->id; } int newInfo(cell *c) { info *i = new info; i->prison = c; i->princess = c; i->alt = c->master->alt; i->id = size(infos); i->bestdist = 0; i->bestnear = OUT_OF_PRISON; infos.push_back(i); assign(i); return i->id; } void newFakeInfo(cell *c) { info *i = new info; i->prison = NULL; i->princess = c; i->alt = NULL; i->id = size(infos); i->bestdist = items[itSavedPrincess] ? OUT_OF_PALACE : OUT_OF_PRISON; i->bestnear = 0; infos.push_back(i); assign(i); } info *getPrisonInfo(cell *c) { if(euclid) return NULL; if(c->land != laPalace) return NULL; if(!c->master->alt) return NULL; int ev = c->master->alt->alt->emeraldval; // NEWYEARFIX if(ev < 0 || ev >= size(infos)) return NULL; if(infos[ev]->alt != c->master->alt->alt) return NULL; return infos[ev]; } info *getPrincessInfo(cell *c) { for(int i=0; iprincess == c) { while(i) { infos[i]->id = i-1; assign(infos[i]); infos[i-1]->id = i; assign(infos[i-1]); swap(infos[i], infos[i-1]); i--; } return infos[i]; } return NULL; } int dist(cell *c) { if(c->land != laPalace && c->land != laDungeon) return OUT_OF_PALACE; else if(quotient || sphere || torus) return OUT_OF_PRISON; else if(euclid) return celldistAlt(c); else if(!c->master->alt) return OUT_OF_PRISON; else return celldistAlt(c); } void clear() { for(int i=0; imonst = moPrincessArmed; c->stuntime = 0; c->hitpoints = palaceHP(); drawFlash(c); playSound(c, princessgender() ? "heal-princess" : "heal-prince"); info *inf = NULL; for(int i=0; iprincess && infos[i]->bestdist == OUT_OF_PALACE && isPrincess(infos[i]->princess->monst)) inf = infos[i]; } if(inf) { inf->princess->monst = moNone; inf->princess = c; } else newFakeInfo(c); return true; } void bringBack() { if(bringBackAt(cwt.c->mov[cwt.spin])) return; for(int i=1; i i->bestdist) { i->bestdist = newdist; // printf("Improved dist to %d\n", newdist); if(newdist == OUT_OF_PALACE) { if(!princess::saved) #if CAP_INV if(!inv::on || !inv::usedForbidden) #endif achievement_gain("PRINCESS1"); princess::saved = true; princess::everSaved = true; if(inv::on && !princess::reviveAt) princess::reviveAt = gold(NO_LOVE); items[itSavedPrincess]++; } if(newdist == OUT_OF_PRISON && princess::challenge) { addMessage(XLAT("Congratulations! Your score is %1.", its(i->value))); achievement_gain("PRINCESS2"); if(!cheater) achievement_score(36, i->value); showMissionScreen(); } } if(i->princess->land == laDungeon && !saved && !nodungeon) { addMessage(XLAT("%The1 says, \"not this place, it looks even worse...\"", moPrincess)); nodungeon = true; } } void save(cell *princess) { if(euclid) return; princess::info *i = princess::getPrincessInfo(princess); if(!i || i->bestdist <= 3) princess->monst = moNone; else if(i) setdist(i, OUT_OF_PRISON); } void move(cell *ct, cell *cf) { if(euclid) return; princess::info *i = princess::getPrincessInfo(cf); if(!i) { // note: OK if mapediting or loading printf("Warning: unknown princess\n"); if(!cheater) addMessage("Warning: unknown princess (that's a bug, please report)"); } else { i->princess = ct; setdist(i, dist(ct)); // printf("newdist = %d (vs %d)\n", newdist, i->bestdist); } } void mouseSqueak(cell *c) { eMonster m = c->monst; info *i = getPrisonInfo(c); int d = dist(c); playSound(c, "mousesqueak", 40); if(!i) addMessage(XLAT("%The1 squeaks in a confused way.", m)); else if(i->bestdist >= 6) addMessage(XLAT("%The1 squeaks gratefully!", m)); else if(!i->princess) addMessage(XLAT("%The1 squeaks hopelessly.", m)); else if(d > 120) addMessage(XLAT("%The1 squeaks in despair.", m)); else if(d > 90) addMessage(XLAT("%The1 squeaks sadly.", m)); else if(d > 60) addMessage(XLAT("%The1 squeaks with hope!", m)); else if(d > 30) addMessage(XLAT("%The1 squeaks happily!", m)); else addMessage(XLAT("%The1 squeaks excitedly!", m)); } void line(cell *c) { int d = (euclid || c->master->alt) ? celldistAlt(c) : 200; eMonster m = c->monst; static int msgid = 0; playSound(c, princessgender() ? "speak-princess" : "speak-prince"); retry: if(msgid >= 127) msgid = 0; bool inpalace = c->land == laPalace || c->land == laDungeon; if(msgid == 0 && d < 20 && inpalace) { addMessage(XLAT("%The1 kisses you, and begs you to bring %him1 away from here.", m)); } else if(msgid == 1 && d >= 20 && inpalace && !peace::on) { if(m == moPrincess) addMessage(XLAT("\"I want my revenge. Stun a guard and leave him for me!\"", m)); else addMessage(XLAT("\"That felt great. Thanks!\"", m)); } else if(msgid == 2 && d >= 70 && inpalace) { addMessage(XLAT("\"Bring me out of here please!\"", m)); } else if(msgid == 3 && !inpalace) { addMessage(XLAT("%The1 kisses you, and thanks you for saving %him1.", m)); } else if(msgid == 4 && !inpalace && m == moPrincess && !peace::on) { addMessage(XLAT("\"I have been trained to fight with a Hypersian scimitar, you know?\"", m)); } else if(msgid == 16 && !inpalace) { addMessage(XLAT("\"I would love to come to your world with you!\"", m)); } else if(msgid == 20 && !inpalace) { addMessage(XLAT("\"I do not like butterflies. They are treacherous.\"", m)); } else if(msgid == 32 && !inpalace) { addMessage(XLAT("\"Straight lines stay close to each other forever, this is so romantic!\"", m)); } else if(msgid == 40 && !inpalace) { addMessage(XLAT("\"I hate roses.\"", m)); } else if(msgid == 48 && !inpalace) { addMessage(XLAT("\"Maps... Just like the world, but smaller... how is that even possible?!\"", m)); } else if(msgid == 64) { addMessage(XLAT("\"In this world there is plenty of space for everyone. We do not need wars.\"", m)); } else if(msgid == 65) { addMessage(XLAT("\"Only the stupid hyperbugs do not understand this.\"", m)); } else if(msgid == 72 && !inpalace) { addMessage(XLAT("\"I have once talked to a Yendorian researcher... he was only interested in infinite trees.\"", m)); } else if(msgid == 73 && !inpalace) { addMessage(XLAT("\"Infinite trees are boring. I prefer other graphs.\"", m)); } else if(msgid == 80) { addMessage(XLAT("\"Are there Temples of Cthulhu in your world? Why not?\"", m)); } else { msgid++; goto retry; } msgid++; } void playernear(cell *c) { info *i = getPrisonInfo(c); int d = dist(c); // if(i) printf("d=%d bn=%d\n", d, i->bestnear); if(i && d < i->bestnear) { if(i->bestnear > 100 && d <= 100) { i->value = items[itPalace]; if(princess::challenge) addMessage(XLAT("Hardness frozen at %1.", its(i->value))); } i->bestnear = d; } } } namespace clearing { struct clearingdata { cell *root; int dist; }; bool buggyplant = false; std::map bpdata; int plantdir(cell *c) { generateAlts(c->master); for(int i=0; i<7; i++) generateAlts(c->master->move[i]); int d = celldistAlt(c); if(purehepta) { for(int i=0; i<7; i++) { cell *c2 = createMov(c, i); if(!pseudohept(c2) && celldistAlt(c2) == d-1) return i; } for(int i=0; i<7; i++) { cell *c2 = createMov(c, i); if(celldistAlt(c2) == d-1) return (i+1) % 7; } } for(int i=1; i<6; i+=2) { cell *c2 = createMov(c, i); if(celldistAlt(c2) == d-1) return i; } int quseful = 0, tuseful = 0; for(int i=1; i<6; i+=2) { cell *c2 = c->mov[i]; if(celldistAlt(c2) == d) { bool useful = false; for(int j=1; j<6; j++) { cell *c3 = createMov(c2, j); if(celldistAlt(c3) == d-1) useful = true; } if(useful) quseful++, tuseful += i; } } if(quseful == 1) return tuseful; if(quseful == 2) { int i; if(tuseful == 3+5) i = 3; if(tuseful == 5+1) i = 5; if(tuseful == 1+3) i = 1; if((d & 7) < 4) i = (i+2) % 6; return i; } printf("error in plantdir\n"); return 1; } vector onpath; vector pdir; vector rpath; void generate(cell *c) { if(buggyplant) return; if(euclid) { if(torus) return; if(pseudohept(c)) return; c->monst = moMutant; eucoord x, y; decodeMaster(c->master, x, y); int xco = x * 2 + y + 1; c->stuntime = (8-xco/2) & 15; // 2, 4, 5, 7 if(pseudohept(createMov(c, 0))) c->mondir = 1 + hrand(2) * 4; else c->mondir = 0; return; } // cell *oc = c; if(!euclid) generateAlts(c->master); if(pseudohept(c)) return; heptagon *a = euclid ? NULL : c->master->alt->alt; clearingdata& bd(bpdata[a]); if(!bd.root) { bd.root = c; bd.dist = 8; } onpath.clear(); pdir.clear(); rpath.clear(); int steps = 0; int ds; while(true) { if(c == bd.root) {ds = bd.dist; break; } // printf("R %4d C %4d\n", celldistAlt(bd.root), celldistAlt(c)); if(celldistAlt(c) > celldistAlt(bd.root)) { if(c->mpdist <= 6) { if(c->monst != moMutant) return; // already cut! // ... else simply extend it ds = c->stuntime; break; } int d = plantdir(c); steps++; onpath.push_back(c); pdir.push_back(d); // printf("c [%4d] %p -> %p\n", celldistAlt(c), c, c->mov[d]); c = c->mov[d]; } else { bd.dist--; if(bd.dist < -1000) { for(int i=0; iitem = itBuggy; for(int i=0; i<(int) rpath.size(); i++) rpath[i]->item = itBuggy; buggyplant = true; printf("buggygen\n"); return; } rpath.push_back(bd.root); // printf("r [%4d] %p -> %p\n", celldistAlt(bd.root), bd.root, bd.root->mov[plantdir(bd.root)]); bd.root = bd.root->mov[plantdir(bd.root)]; } } // printf("steps = %d dist = %d [%d]\n", steps, bd.dist, oc->mpdist); onpath.push_back(c); pdir.push_back(plantdir(c)); while(steps >= 0) { c = onpath[steps]; if(steps == 0) { c->monst = moMutant; c->mondir = pdir[steps]; if(pdir[steps] != plantdir(c)) { printf("pdir i/ plantdir\n"); exit(1); } c->stuntime = ds; } if(c->mpdist <= 7 && c->monst != moMutant) break; steps--; ds++; } } } namespace whirlpool { bool escaped = false; // escaped the Whirlpool? // next == +1 -> next // next == -1 -> prev cell *get(cell *c, int next) { int i = 0; if(!euclid && !c->master->alt) return NULL; int d = celldistAlt(c); int d2; while(true) { if(i == c->type) return NULL; if(c->mov[i] && (d2 = celldistAlt(c->mov[i])) != d) break; i++; } if(i == c->type) return NULL; if(d>d2) next = -next; for(int j=1; jtype; j++) { cell *c2 = c->mov[(i+420+next*j) % c->type]; if(celldistAlt(c2) == d) return c2; } return NULL; } void build(vector& whirlline, int d) { again: cell *at = whirlline[size(whirlline)-1]; cell *prev = whirlline[size(whirlline)-2]; for(int i=0; itype; i++) if(at->mov[i] && (euclid || at->mov[i]->master->alt) && celldistAlt(at->mov[i]) == d && at->mov[i] != prev) { whirlline.push_back(at->mov[i]); goto again; } } void generate(cell *wto) { if(wto->wall == waBoat || wto->monst) return; if(hrand(35000) < 40 + items[itWhirlpool] + yendor::hardness()) wto->monst = moCShark; else if(hrand(5000) < 500) wto->wall = waBoat; if(wto->wall == waBoat && (euclid || wto->master->alt)) { int d = celldistAlt(wto); if(yendor::on) d -= 200; // 250 : hard if(hrand(5000) < 60 + 3 * items[itWhirlpool] + yendor::hardness()) wto->monst = moPirate; if(hrand(5000) < 20 && d < -20 && !tactic::on && !inv::on) wto->item = itOrbSafety; else if(hrand(5000) < 20 && d < -20 && !tactic::on && markOrb(itOrbLuck)) wto->item = itOrbSafety; else if(hrand(5000) < 20*PRIZEMUL && d < -20) placePrizeOrb(wto); else if(!inv::on && items[itWhirlpool] >= 10 && hrand(5000) < 20 && d < -15) wto->item = itOrbWater; else if(d<-10 && hrand(5000) < 1000-d) wto->item = itWhirlpool; } } void whirlMove(cell *wto, cell *wfrom) { // monsters don't move if(wfrom && (isPlayerOn(wfrom) || wfrom->monst)) return; // disappear if(!wto) { wfrom->wall = waSea; wfrom->item = itNone; } if(wfrom && wto && wfrom->wall == waBoat && wto->wall == waSea && !wto->monst) { wfrom->wall = waSea; wto->wall = waBoat; wto->mondir = neighborId(wto, wfrom); animateMovement(wfrom, wto, LAYER_BOAT); } if(wfrom && wto && wfrom->item && !wto->item && wfrom->wall != waBoat) { // Keys and Orbs of Yendor never disappear! if(wfrom->item == itKey || wfrom->item == itOrbYendor) for(int i=0; itype; i++) createMov(wto, i); moveItem(wfrom, wto, false); } if(wto && !wfrom) generate(wto); } void moveAt(cell *c) { if(c->land != laWhirlpool) return; if(eq(c->aitmp, sval)) return; if(!(euclid || c->master->alt)) return; cell *c2 = get(c, 1); if(!c2) return; int d = celldistAlt(c); vector whirlline; whirlline.push_back(c); whirlline.push_back(c2); build(whirlline, d); reverse(whirlline.begin(), whirlline.end()); build(whirlline, d); int z = size(whirlline); for(int i=0; iaitmp = sval; whirlMove(NULL, whirlline[0]); for(int i=0; i> mirrors; static const int LIGHTNING = -1; // passed instead of cpid bool noMirrorOn(cell *c) { return c->monst || (!shmup::on && isPlayerOn(c)) || (geometry != gQuotient2 && geometry != gTorus && c->cpdist > 7); } bool cellMirrorable(cell *c) { if(noMirrorOn(c)) return false; return c->wall == waNone || c->wall == waCavefloor || isAlchAny(c) || c->wall == waFrozenLake || c->wall == waDeadfloor || c->wall == waDeadfloor2 || c->wall == waGiantRug || c->wall == waCIsland || c->wall == waCIsland2 || c->wall == waGargoyleFloor || c->wall == waRubble || c->wall == waGargoyleBridge || c->wall == waTempFloor || c->wall == waTempBridge || c->wall == waMirrorWall; } void destroyKilled() { int j = 0; for(int i=0; imonst == moMimic) mirrors[j++] = mirrors[i]; mirrors.resize(j); } void unlist() { for(auto& m: mirrors) if(m.second.c->monst == moMimic) m.second.c->monst = moNone; } void list() { for(auto& m: mirrors) m.second.c->monst = moMimic; } void destroyAll() { unlist(); mirrors.clear(); } void createMirror(cellwalker cw, int cpid) { if(!shmup::on && inmirror(cw)) cw = reflect(cw); if(cpid == LIGHTNING) castLightningBolt(cw); else if(cellMirrorable(cw.c)) { for(auto& m: mirrors) if(m == make_pair(cpid,cw)) return; mirrors.emplace_back(cpid, cw); } } void createMirrors(cellwalker cw, int cpid) { cw.mirrored = !cw.mirrored; cell *c = cw.c; for(int i=0; itype; i++) { cwstep(cw); if(cw.c->type == c->type) { cwspin(cw, i); createMirror(cw, cpid); cwspin(cw, -i); } cwstep(cw); cwspin(cw, 1); } } void createMirages(cellwalker cw, int cpid) { if(purehepta) { for(int i=0; itype; i++) { cellwalker C2 = cw; cwstep(C2); cwspin(C2, 3); cwstep(C2); cwspin(C2, 5); cwstep(C2); cwspin(C2, 3); cwspin(C2, -i); createMirror(C2, cpid); cwspin(cw, 1); } return; } for(int i=0; i<6; i++) { cwstep(cw); if(cw.c->type == 6) { cwspin(cw, 2); cwstep(cw); cwspin(cw, 4-i); createMirror(cw, cpid); cwspin(cw, 6-4+i); cwstep(cw); cwspin(cw, 2); cwstep(cw); cwspin(cw, 2-i); createMirror(cw, cpid); cwspin(cw, 6-2+i); cwstep(cw); cwspin(cw, 2); } cwstep(cw); cwspin(cw, 1); } } void createHere(cellwalker cw, int cpid) { if(!cw.c) return; if(cw.c->wall == waCloud) createMirages(cw, cpid); if(cw.c->wall == waMirror) createMirrors(cw, cpid); } void breakMirror(cellwalker cw, int pid) { if(!cw.c) return; cell *c = cw.c; if(c->wall == waMirror || c->wall == waCloud) { drawParticles(c, winf[c->wall].color, 16); playSound(c, "pickup-mirror", 50); if(pid >= 0 && (cw.c->land == laMirror || cw.c->land == laMirrorOld)) { dynamicval d(multi::cpid, pid); gainShard(cw.c, c->wall == waMirror ? "The mirror shatters!" : "The cloud turns into a bunch of images!"); } c->wall = waNone; } } bool isKilledByMirror(cell *c) { for(auto& m: mirrors) if(cwpeek(m.second, 0) == c && canAttack(m.second.c, moMimic, c, c->monst, 0)) return true; return false; } void go(bool fwd) { int tk = tkills(); int nummirage = 0; int j = 0; for(int i=0; imonst) { c->monst = moMimic; if(!peace::on && canAttack(c,moMimic,c2,c2->monst, 0)) attackMonster(c2, AF_MSG | AF_ORSTUN, moMimic); c->monst = moNone; } if(c2->wall == waBigTree) c2->wall = waSmallTree; else if(c2->wall == waSmallTree) c2->wall = waNone; if(fwd) { if(noMirrorOn(c2) || !passable(c2, c, P_MONSTER | P_MIRROR | P_MIRRORWALL)) { survive = false; continue; } c->monst = moMimic; moveMonster(c2, c); c2->monst = moNone; empathyMove(c, c2, neighborId(c2, c)); m.second = cw2; } } if(survive) { mirrors[j++] = m; } } mirrors.resize(j); achievement_count("MIRRORKILL", tkills(), tk); achievement_count("MIRAGE", nummirage, 0); } void act(int d, int flags) { destroyKilled(); unlist(); if(multi::players == 1) multi::cpid = 0; bool spinning = flags & (multi::players > 1 ? SPINMULTI : SPINSINGLE); if(spinning && d) { for(auto& m: mirrors) if(m.first == multi::cpid) cwspin(m.second, d); } if(flags & ATTACK) go(flags & GO); list(); } void breakAll() { destroyKilled(); unlist(); if(numplayers() == 1) createHere(cwt, 0); else for(int i=0; itype == 7) return c->bardir; int icount = 0, isum = 0; for(int i=0; i<6; i+=2) { if(createMov(c, i)->bardir == c->spin(i)) icount++, isum+=i; } if(icount != 1) return -1; return isum; } pair traceback(vector& v, cellwalker cw) { bool goout = false; for(int i=size(v)-1; i>=0; i--) { if(v[i]) cwspin(cw, -v[i]); else { cwstep(cw); if(cw.c->land == laMirrorWall || cw.c->land == laMirror) goout = true; } } return make_pair(goout, cw); } int depth(cell *c) { return c->landparam & 255; } cellwalker reflect0(cell *c) { cellwalker cw(c, 0, false); int stepcount = 0; cellwalker cwcopy = cw; static vector v; v.clear(); while(true) { if(!inmirror(cw)) break; stepcount++; if(stepcount > 10000) { return cw; } cell *c0 = cwpeek(cw, 0); int go = 0; if(!inmirror(c0)) go = 2; else if(depth(c0) && depth(c0) < depth(cw.c)) go = 1; if(go) { v.push_back(0); cwstep(cw); if(go == 2) break; } else { v.push_back(1); cwspin(cw, 1); } } if(cw.c->land == laMirrorWall || cw.c->land == laMirrorWall2) { if(cw.c->type == 7) { while(cw.spin != cw.c->bardir) { cwspin(cw, 1); v.push_back(1); stepcount++; if(stepcount > 10000) { printf("failhep\n"); return cw; } } if(purehepta && cwpeek(cw,0) == cwcopy.c) v.pop_back(); if(purehepta && cwpeek(cw,3)->land == laMirrored && cwpeek(cw,2)->land == laMirrorWall) { cw.mirrored = !cw.mirrored; auto p = traceback(v, cw); if(p.first) return p.second; cwspin(cw, 2); v.push_back(2); cwstep(cw); v.push_back(0); cwspin(cw, 3); v.push_back(3); } } else { while(cwpeek(cw,0)->type != 7) { cwspin(cw, 1); v.push_back(1); } int icount = 0; for(int i=0; i<3; i++) { if(cwpeek(cw, 0)->bardir == cw.c->spin(cw.spin)) icount++; cwspin(cw, 2); } if(icount >= 2) { cellwalker cwcopy = cw; for(int a=0; a<3; a++) for(int m=0; m<2; m++) { cellwalker cw = cwcopy; if(m) cw.mirrored = !cw.mirrored; cwspin(cw, a*2); auto p = traceback(v,cw); if(p.first) return p.second; } printf("icount >= 2 but failed\n"); return cw; } while(cwpeek(cw, 0)->bardir != cw.c->spin(cw.spin)) { stepcount++; if(stepcount > 10000) { printf("fail2\n"); return cw; } cwspin(cw, 2); v.push_back(1); v.push_back(1); } } } else v.pop_back(); cw.mirrored = !cw.mirrored; cw = traceback(v,cw).second; return cw; } static const int CACHESIZE = 1<<12; // must be a power of 2 static const int CACHEMASK = CACHESIZE-1; pair cache[CACHESIZE]; int nextcache; void clearcache() { for(int i=0; ilandparam >> 8) & CACHEMASK; if(cache[cid].first != cw.c) { cid = nextcache++; nextcache &= CACHEMASK; cw.c->landparam &= ~ (CACHEMASK << 8); cw.c->landparam |= (nextcache << 8); cache[cid].first = cw.c; cache[cid].second = reflect0(cw.c); } cellwalker res = cache[cid].second; cwspin(res, cw.spin); if(cw.mirrored) res.mirrored = !res.mirrored; return res; } } namespace hive { struct buginfo_t { cell *where; short dist[BUGCOLORS]; }; vector buginfo; vector bugqueue[BUGCOLORS]; vector bugqueue4[BUGCOLORS]; struct bugtomove_t { int dist, moves, index; bugtomove_t(int d, int m, int i) { dist=d; moves=m; index=i; } }; bool operator < (const bugtomove_t& m1, const bugtomove_t& m2) { if(m1.dist != m2.dist) return m1.dist < m2.dist; if(m1.moves != m2.moves) return m1.moves < m2.moves; return false; } vector bugtomove; vector deadbug; vector bugcellq; int bugcount[BUGCOLORS]; bool isBugEnemy(cell *c, int k) { if(isPlayerOn(c) && !invismove) return true; if(!c->monst) return false; if(c->monst == moBug0+k) return false; if(isIvy(c)) return false; return true; } // list bugs and targets for each color #define BUGINF 29999 void bugQueueInsert(int k, int i, int d) { if(buginfo[i].dist[k] > d) { if(buginfo[i].dist[k] != BUGINF) { printf("%d -> %d\n", buginfo[i].dist[k], d); } buginfo[i].dist[k] = d; bugqueue[k].push_back(i); } } void bugcell(cell *c) { short& i(c->aitmp); if(i >= 0 && i < size(buginfo) && buginfo[i].where == c) return; i = size(buginfo); buginfo.resize(i+1); buginfo_t& b(buginfo[i]); b.where = c; for(int k=0; ktype; dir++) { cell *c2 = c->mov[dir]; if(c2 && isBugEnemy(c2,k) && canAttack(c,eMonster(moBug0+k),c2,c2->monst, AF_TOUGH | AF_NOSHIELD | AF_GETPLAYER)) { if(isBug(c2)) havebug = true; else haveother = true; } } if(havebug) bugQueueInsert(k, i, 0); else if(haveother) bugqueue4[k].push_back(i); } /*// bugs communicate if the distance is at most 2 // also all nearby cells are inserted to the buginfo structure if(size(buginfo) < 30000) { for(int dir=0; dirtype; dir++) { cell *c2 = c->mov[dir]; if(c2) { // if(isBug(c)) bugcellq.push_back(c2); => does not help... for(int t=0; ttype; t++) if(c2->mov[t] && isBug(c2->mov[t])) bugcellq.push_back(c2), bugcellq.push_back(c2->mov[t]); } } }*/ // use pheromones! if(c->land == laHive && c->landparam > 1 && c->wall != waWaxWall) { c->landparam --; for(int dir=0; dirtype; dir++) { cell *c2 = c->mov[dir]; if(c2) { for(int t=0; ttype; t++) if(c2->mov[t]) bugcellq.push_back(c2), bugcellq.push_back(c2->mov[t]); } } } } int last_d = -1; void handleBugQueue(int k, int t) { int i = bugqueue[k][t]; buginfo_t& b(buginfo[i]); cell *c = b.where; int d = b.dist[k]; last_d = d; int goodmoves = 0; for(int dir=0; dirtype; dir++) { cell *c2 = c->mov[dir]; if(!c2) continue; if(c2->aitmp < 0 || c2->aitmp >= size(buginfo)) continue; if(!passable(c, c2, P_MONSTER)) continue; int j = c2->aitmp; if(buginfo[j].where != c2) continue; if(buginfo[j].dist[k] < d) goodmoves++; bugQueueInsert(k, j, d+1); } if(isBug(c) && c->monst == moBug0+k) { bugcount[c->monst - moBug0]++; bugtomove.push_back(bugtomove_t(d,goodmoves,i)); } } #include bool fightspam(cell *c) { return c->cpdist >= 7 || isMetalBeast(c->monst) || c->monst == moSkeleton || isIvy(c->monst) || isMutantIvy(c->monst); } void movebugs() { buginfo.clear(); for(int k=0; k check; for(int t=0; tstuntime) continue; eMonster m = c->monst; int k = (m - moBug0) % BUGCOLORS; int gmoves[8], q=0, bqual = -1; if(againstRose(c, NULL)) bqual = -40; for(int dir=0; dirtype; dir++) { cell *c2 = c->mov[dir]; int qual = -10; if(!c2) continue; else if(againstRose(c, c2)) qual = -50; else if(canAttack(c, m, c2, c2->monst, AF_GETPLAYER)) qual = c2->monst == moDeadBug ? -60: isBugEnemy(c2,k) ? 2 : -20; else if(!passable(c2, c, 0)) qual = passable(c2, c, P_DEADLY) ? -30 : -60; else if(c2->aitmp < 0 || c2->aitmp >= size(buginfo)) qual = -15; else if(buginfo[c2->aitmp].where != c2) qual = -15; else if(buginfo[c2->aitmp].dist[k] < b.dist[k]) qual = 1; else if(buginfo[c2->aitmp].dist[k] == b.dist[k]) qual = 0; // printf("%d->#%d %d: %d\n", i, dir, c2->tmp, qual); if(qual > bqual) bqual = qual, q=0; if(qual == bqual) gmoves[q++] = dir; } if(!q) { if(c->land == laHive) c->landparam += 3; continue; } int d = gmoves[hrand(q)]; cell *c2 = c->mov[d]; if(c2->monst || isPlayerOn(c2)) { eMonster killed = c2->monst; if(isPlayerOn(c2)) killed = moPlayer; if(isBug(killed)) battlecount++; else if(killed != moPlayer && !fightspam(c2)) addMessage(XLAT("%The1 fights with %the2!", c->monst, killed)); attackMonster(c2, AF_ORSTUN | AF_GETPLAYER, c->monst); // killMonster(c); if(isBug(killed)) { c2->monst = moDeadBug, deadbug.push_back(c2); bugcount[killed - moBug0]--; } // c->monst = moDeadBug, deadbug.push_back(c); } else { moveMonster(c2, c); // pheromones! if(c->land == laHive && c->landparam < 90) c->landparam += 5; if(c2->land == laHive && c2->landparam < 90) c2->landparam += 5; // if(isHive(c2->land)) c2->land = eLand(laHive0+k); /* if(c2->item == itRoyalJelly && !isQueen(m)) { // advance! c2->monst = eMonster(m+BUGCOLORS); c2->item = itNone; } */ } } // cleanup for(int i=0; imonst = moNone; if(battlecount) addMessage(XLAT("The Hyperbugs are fighting!")); int maxbug = 0; for(int k=0; k maxbug) maxbug = bugcount[k]; achievement_count("BUG", maxbug, 0); } void bugcitycell(cell *c, int d) { short& i = c->aitmp; if(i >= 0 && i < size(buginfo) && buginfo[i].where == c) return; i = size(buginfo); buginfo_t b; b.where = c; b.dist[0] = d; buginfo.push_back(b); } void createBugArmy(cell *c) { int k = randomHyperbug() - moBug0; int minbugs = 50, maxbugs = 50; int var = 5 + items[itRoyalJelly]; if(var>25) var=25; // minbugs += 100; maxbugs += 100; minbugs -= var; maxbugs += var; maxbugs += items[itRoyalJelly]; int numbugs = minbugs + hrand(maxbugs - minbugs + 1); /* int i = items[itRoyalJelly]; int chance = 20 + 25 * i + 9000; // i=0: 16% // i=10: 73% // i=50: 1270 vs 6000 eMonster m = eMonster(moBug0 + hrand(BUGCOLORS)); if(c->wall) return; for(int i=0; itype; i++) { cell *c2 = createMov(c,i); if(hrand(100+chance) < chance) { if(!c2->wall) c2->monst = m; for(int j=2; j<=c2->type-2; j++) { int jj = (j+c->spn[i]) % c2->type; cell *c3 = createMov(c2, jj); if(hrand(6000+chance) < chance && !c3->wall) c3->monst = m; } } } c->monst = eMonster(m + BUGCOLORS); */ int gdir = -1; for(int i=0; itype; i++) { if(c->mov[i] && c->mov[i]->mpdist < c->mpdist) gdir = i; } if(!gdir) return; cellwalker bf(c, gdir); int radius = 9; if(chaosmode) radius = 5; for(int i=2; itype == 6) cwspin(bf, 3); else cwspin(bf, 3 + hrand(2)); cwstep(bf); } cell *citycenter = bf.c; buginfo.clear(); // mark the area with BFS bugcitycell(citycenter, 0); for(int i=0; iland != laHive && c->land != laNone) return; if(c->bardir != NODIR) return; if(c->land == laHive && c->landparam >= 100) return; // bfs if(d < radius) for(int t=0; ttype; t++) bugcitycell(createMov(c,t), d+1); } // place everything for(int i=0; iwall == waNone) c->item = itRoyalJelly; preventbarriers(c); if(d == 9 || d == 6 || d == 3) c->barleft = eLand(d/3), c->barright = eLand(k); else c->barleft = laNone; if(numbugs && c->wall == waNone) c->monst = eMonster(moBug0 + k), numbugs--; c->land = laHive; // prevent barriers if(c->mpdist == INFD) c->mpdist = BUGLEV; } } } inline float& HEAT(cell *c) { return c->LHU.heat; } namespace heat { vector vinefires; vector > rosefires; double absheat(cell *c) { if(c->land == laCocytus) return HEAT(c) -.6; if(c->land == laIce) return HEAT(c) -.4; return 0; } double celsius(cell *c) { return absheat(c) * 60; } // adjust to the improved heat transfer algorithm in 9.4 const float FIX94 = 1.5; void processheat(double rate = 1, bool tick = true) { if(markOrb(itOrbSpeed)) rate /= 2; int oldmelt = kills[0]; vector offscreen2; for(int i=0; icpdist > 7) { bool readd = false; if(isIcyLand(c)) { if(HEAT(c) < .01 && HEAT(c) > -.01) HEAT(c) = 0; else { HEAT(c) *= 1 - rate/10; readd = true; } } if(hasTimeout(c)) { useup(c); if(hasTimeout(c)) readd = true; } if(readd) offscreen2.push_back(c); } } offscreen.clear(); swap(offscreen, offscreen2); /* if(cwt.c->heat > .5) cwt.c->heat += .3; if(cwt.c->heat > 1.) cwt.c->heat += .3; if(cwt.c->heat > 1.4) cwt.c->heat += .5; */ for(int i=0; iland == laCocytus && shmup::on) ? rate/3 : rate; if(purehepta) xrate *= 1.7; if(!shmup::on) xrate /= FIX94; if(isIcyLand(c)) HEAT(c) += (markOrb(itOrbWinter) ? -1.2 : 1.2) * xrate; } vinefires.clear(); rosefires.clear(); vector& allcells = currentmap->allcells(); int dcs = size(allcells); vector hmods(dcs, 0); for(int i=0; iland == laCocytus && shmup::on) ? 1/3. : 1; if(purehepta) xrate *= 1.7; if(!shmup::on) xrate /= FIX94; if(c->cpdist > 7 && !doall) break; if(hasTimeout(c)) { if(tick) useup(c); readd = true; } if(isFire(c) && tick) { if(c->wall != waPartialFire) for(int i=0; itype; i++) { cell *c2 = c->mov[i]; if(!c2) continue; if(c2->wall == waNone && c2->land == laRose && c->wparam >= 10) rosefires.push_back(make_pair(c2, c->wparam)); if(c2->wall == waFire && c2->land == laRose && c->wparam >= 10 && c2->wparam < c->wparam/2) rosefires.push_back(make_pair(c2, c->wparam)); if(againstWind(c, c2) && c->wall != waEternalFire && c->wparam >= 10) { if(isFire(c2)) { if(c2->wparam < c->wparam/2) { rosefires.push_back(make_pair(c2, c->wparam)); } } else { rosefires.push_back(make_pair(c2, c->wparam)); useup(c); } } if(c2->wall == waVinePlant) vinefires.push_back(c2); if(c2->wall == waRose) vinefires.push_back(c2); if(c2->wall == waSaloon) vinefires.push_back(c2); if(c2->wall == waSmallTree && c2->land != laDryForest) vinefires.push_back(c2); if((c2->wall == waWeakBranch || c2->wall == waCanopy || c2->wall == waTrunk || c2->wall == waSolidBranch || c2->wall == waBigBush || c2->wall == waSmallBush)) vinefires.push_back(c2); if(c2->wall == waBonfireOff) activateActiv(c2, false); // both halfvines have to be near fire at once if(cellHalfvine(c2) && c->mov[(i+1)%c->type]->wall == c2->wall) vinefires.push_back(c2); } // two semifires are required to spread if(c->wall == waPartialFire) for(int i=0; itype; i++) { cell *c2 = c->mov[i]; if(c2 && (c2->wall == waVinePlant)) { for(int j=0; jtype; j++) if(c2->mov[j] && c2->mov[j]->wall == waPartialFire && c2->mov[j] != c) vinefires.push_back(c2); } } } if(isIcyLand(c)) { ld hmod = 0; if(c->monst == moRanger) hmod += 3 * xrate; if(c->monst == moDesertman) hmod += 4 * xrate; if(c->monst == moMonkey) hmod += xrate; if(c->wall == waDeadTroll) hmod -= 2 * xrate; if(c->wall == waDeadTroll2) hmod -= 1.5 * xrate; if(c->wall == waBigStatue) hmod -= .5 * xrate; if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM) hmod += (c->land == laCocytus ? 1.5 : 10) * xrate; if(c->monst == moGreaterShark) hmod += 2 * xrate; if(c->monst == moCultist) hmod += 3 * xrate; if(c->monst == moCultistLeader) hmod += 4 * xrate; if(c->monst == moPyroCultist) hmod += 6 * xrate; if(c->monst == moFireFairy) hmod += 6 * xrate; if(c->monst == moFireElemental) hmod += 8 * xrate; if(isDragon(c->monst)) hmod += 2 * xrate; if(c->monst == moGhost) hmod -= xrate; if(c->monst == moFriendlyGhost) hmod -= xrate; if(c->monst == moSkeleton) hmod -= .2 * xrate; if(c->monst == moDraugr) hmod -= .75 * xrate; if(c->monst == moWaterElemental) hmod -= xrate; if(c->monst == moAirElemental) hmod -= .4 * xrate; if(isFire(c)) hmod += 4 * xrate; if(isPrincess(c->monst)) hmod += (markEmpathy(itOrbWinter) ? -1.2 : 1.2) * xrate; forCellEx(ct, c) if(!isIcyLand(ct) && isFire(ct)) hmod += xrate*.1; for(int j=0; jtype; j++) if(c->mov[j]) { if(!isIcyLand(c->mov[j])) { // make sure that we can still enter Cocytus, // it won't heat up right away even without Orb of Winter or Orb of Speed if(isPlayerOn(c->mov[j]) && (c->land == laIce || markOrb(itOrbWinter))) hmod += (markOrb(itOrbWinter) ? -1.2 : 1.2) / 4 * xrate; continue; } ld hdiff = absheat(c->mov[j]) - absheat(c); hdiff /= 10; if(shmup::on && (c->land == laCocytus || c->mov[j]->land == laCocytus)) hdiff /= 3; // if(c->mov[j]->cpdist > 7 && !quotient) hdiff += -HEAT(c) / 30; hmod += hdiff; } hmods[i] = hmod; } if((readd || HEAT(c)) && !doall) offscreen.push_back(c); } #define MELTCOLOR 0xA04040 for(int i=0; imonst == moCrystalSage && HEAT(c) >= SAGEMELT) { addMessage(XLAT("%The1 melts away!", c->monst)); fallMonster(c); } if(c->wall == waIcewall && HEAT(c) > .4) drawParticles(c, MELTCOLOR, 4, 60), c->wall = waNone, kills[0]++; if(c->wall == waFrozenLake && HEAT(c) > (c->land == laCocytus ? .6 : .4)) drawParticles(c, MELTCOLOR, 4, 60), playSound(c, "trapdoor", 50), c->wall = waLake, kills[0]++; if(c->wall == waLake && HEAT(c) < (c->land == laCocytus ? -.4 : .4) && c->monst != moGreaterShark) { c->wall = waFrozenLake; if(c->monst == moShark || c->monst == moCShark) { addMessage(XLAT("%The1 is frozen!", c->monst)); fallMonster(c); } } } if(tick) for(int i=0; iwall == waNone && c->land == laRose) makeflame(c, 6, false); else if(c->wall == waVinePlant || c->wall == waSmallTree || c->wall == waSaloon || c->wall == waRose) makeflame(c, 6, false); else if(c->wall == waSolidBranch || c->wall == waTrunk || c->wall == waWeakBranch || c->wall == waCanopy) makeflame(c, 6, false); else if(c->wall == waBigBush || c->wall == waSmallBush) makeflame(c, 6, false); else if(cellHalfvine(c)) destroyHalfvine(c, waPartialFire, 6); } if(tick) for(int i=0; iwall == waNone && c->land == laRose) makeflame(c, qty, false); if(c->wparam < qty) c->wparam = qty; } if(kills[0] != oldmelt) bfs(); } void dryforest() { vector& allcells = currentmap->allcells(); int dcs = size(allcells); for(int i=0; icpdist > 8) break; if(c->land != laDryForest) continue; forCellEx(c2, c) { if(isFire(c2)) { if(!againstWind(c, c2)) c->landparam++; if(againstWind(c2, c)) c->landparam++; } } } for(int i=0; iland != laDryForest) continue; if(c->landparam >= 10) makeflame(c, 10, false), c->landparam = 0; } for(int i=0; icpdist > 8) break; if(c->land != laDryForest) continue; if((c->wall == waBigTree || c->wall == waSmallTree || isFire(c)) && c->landparam >= 1) c->wall = waEternalFire; } } } bool gardener = false; bool lifebrought = false; // was Life brought to the Dead Caves? void livecaves() { vector& allcells = currentmap->allcells(); int dcs = size(allcells); vector bringlife; for(int i=0; icpdist > 8) break; if(c->wall == waCavefloor || c->wall == waCavewall || c->wall == waDeadTroll) { c->aitmp = 0; if(c->monst == moDarkTroll) c->monst = moTroll; if(c->item || c->monst || c->cpdist == 0) continue; for(int j=0; jtype; j++) if(c->mov[j]) { if(c->mov[j]->wall == waDeadfloor) c->aitmp++, bringlife.push_back(c->mov[j]); else if(c->mov[j]->wall == waDeadwall || (c->mov[j]->wall == waDeadfloor2 && !c->mov[j]->monst)) c->aitmp--, bringlife.push_back(c->mov[j]); else if(c->mov[j]->wall == waCavefloor) c->aitmp++; else if(c->mov[j]->wall == waCavewall) c->aitmp--; else if(c->mov[j]->wall == waRubble) c->aitmp--; else if(c->mov[j]->wall == waGargoyle) c->aitmp--; else if(c->mov[j]->wall == waGargoyleFloor) c->aitmp--; else if(c->mov[j]->wall == waGargoyleBridge) c->aitmp--; else if(c->mov[j]->wall == waDeadTroll) c->aitmp -= 5; else if(c->mov[j]->wall == waDeadTroll2) c->aitmp -= 3; else if(c->mov[j]->wall == waPetrified) c->aitmp -= 2; else if(c->mov[j]->wall == waVinePlant) c->aitmp--; else if(chaosmode && c->mov[j]->land != laCaves && c->mov[j]->land != laEmerald) ; else if(c->mov[j]->land == laTrollheim) ; // trollheim floor does not count else if(c->mov[j]->wall != waBarrier) c->aitmp += 5; if(sword::at(c)) c->aitmp += 500; if(c->mov[j]->cpdist == 0 && markOrb(itOrbDigging)) c->aitmp+=100; if(items[itOrbEmpathy] && isFriendly(c->mov[j]) && markEmpathy(itOrbDigging)) c->aitmp+=100; if(c->mov[j]->wall == waThumperOn) c->aitmp+=100; if(c->mov[j]->wall == waFire) c->aitmp+=100; if(c->mov[j]->wall == waBigStatue) c->aitmp-=100; if(c->mov[j]->item && !peace::on) c->aitmp+=2; if(c->mov[j]->monst == moZombie) c->aitmp += 10; if(c->mov[j]->monst == moGhost) c->aitmp += 10; if(c->mov[j]->monst == moTentacleGhost) c->aitmp += 10; if(c->mov[j]->monst == moFriendlyGhost) c->aitmp += 10; if(c->mov[j]->monst == moSkeleton) c->aitmp ++; if(c->mov[j]->monst == moGargoyle) c->aitmp--; if(c->mov[j]->monst == moDraugr) c->aitmp--; if(isDragon(c->mov[j]->monst)) c->aitmp++; if(c->mov[j]->monst == moNecromancer) c->aitmp += 10; if(c->mov[j]->monst == moWormtail) c->aitmp++; if(c->mov[j]->monst == moTentacletail) c->aitmp-=2; if(isIvy(c->mov[j])) c->aitmp--; if(isDemon(c->mov[j])) c->aitmp-=3; // if(c->mov[j]->monst) c->tmp++; // if(c->mov[j]->monst == moTroll) c->tmp -= 3; } } else if(c->land == laLivefjord) { c->aitmp = 0; if(c->monst == moWaterElemental) c->aitmp += 1000; if(isPlayerInBoatOn(c) && markOrb(itOrbWater)) c->aitmp += 1000; if(c->monst == moEarthElemental) c->aitmp -= 1000; if(isPlayerOn(c) && markOrb(itOrbDigging)) c->aitmp -= 1000; for(int j=0; jtype; j++) if(c->mov[j]) { cell *c2 = c->mov[j]; if(c2->wall == waNone || c2->wall == waStrandedBoat) c->aitmp -= (c2->land == laLivefjord ? 1 : 100); if(c2->wall == waTempFloor || c2->wall == waTempBridge) ; else if(c2->wall == waDeadTroll || c2->wall == waDeadTroll2 || c2->wall == waThumperOn || isFire(c2) || snakelevel(c2)) c->aitmp -= 10; else if(c2->wall == waPetrified) c->aitmp -= 10; if(c2->wall == waBigStatue) c->aitmp -= 10; if(c2->wall == waSea || c2->wall == waBoat) c->aitmp += (c2->land == laLivefjord ? 1 : 100); if(c2->monst == moWaterElemental) c->aitmp += 1000; if(isPlayerOn(c2) && c2->wall == waBoat && markOrb(itOrbWater)) c->aitmp += 1000; if(c2->monst == moEarthElemental) c->aitmp -= 1000; if(isPlayerOn(c2) && markOrb(itOrbDigging)) c->aitmp -= 1000; if(items[itOrbEmpathy] && isFriendly(c2) && markEmpathy(itOrbDigging)) c->aitmp -= 1000; if(c2->wall == waBarrier) { bool landbar = false; for(int k=0; ktype; k++) if(c2->mov[k]) { cell *c3 = c2->mov[k]; if(!isSealand(c3->land)) landbar = true; } if(landbar) c->aitmp -= 5; else c->aitmp += 5; } } } } for(int i=0; icpdist > 8) break; if(c->wall == waCavefloor || c->wall == waCavewall) { // if(c->land != laCaves) continue; // if(c->wall == waThumper || c->wall == waBonfire) continue; if(c->aitmp > 0) c->wall = waCavefloor; if(c->aitmp < 0) { c->wall = waCavewall; if(c->land != laCaves && c->land != laDeadCaves && c->land != laEmerald && !gardener) { gardener = true; achievement_gain("GARDENER"); } } } else if(c->land == laLivefjord) { if(c->aitmp > 0 && c->wall == waStrandedBoat) c->wall = waBoat; if(c->aitmp > 0 && c->wall == waNone) { if(c->item && c->cpdist == 1 && markOrb(itOrbWater)) collectItem(c); c->wall = waSea; } if(c->aitmp < 0 && c->wall == waBoat) c->wall = waStrandedBoat; if(c->aitmp < 0 && c->wall == waSea) c->wall = waNone; } } for(int i=0; iland == laDeadCaves && !lifebrought) { lifebrought = true; achievement_gain("LIFEBRINGER"); } if(c->wall == waDeadfloor) c->wall = waCavefloor; if(c->wall == waDeadfloor2) c->wall = waCavewall; if(c->wall == waDeadwall) c->wall = waCavewall; if(c->wall == waCavewall && c->item) c->wall = waCavefloor; if(c->land == laDeadCaves) c->land = laCaves; if(c->item == itSilver) c->item = itGold; if(c->item == itGreenStone) c->item = itOrbLife; if(c->monst == moEarthElemental) { addMessage(XLAT("%The1 is destroyed by the forces of Life!", c->monst)); fallMonster(c); c->item = itOrbDigging; } } } /* evolver */ namespace tortoise { map emap; map babymap; int last; enum tflag { tfShell, tfScute0, tfScute1, tfScute2, tfScute3, tfEdge1, tfEdge, tfEdge3, tfLongNeck, tfFront, tfRear, tfTail, tfEyeHue, tfShellHue, tfScuteHue, tfSkinHue, tfShellSat, tfScuteSat, tfSkinSat, tfShellDark, tfSkinDark, tfCOUNT }; const int numbits = (int) tfCOUNT; const int mask = (1<> i)&1) bi++; return bi; } int getBit(int bits, int id) { return (bits >> id) & 1; } int getRandomBits() { return hrand(1 << numbits); } bool seek() { return items[itBabyTortoise] % 5; } int seekbits; double seekval[numbits]; double currval[numbits]; void update(double& val, double target, int delta) { double d = delta / 300.; if(d>1) d = 1; if(target>val+d) val += d; else if(target res // 1 1 1 => 0 // 1 1 0 => -1 // 1 0 1 => +1 // 1 0 0 => 0 // 0 1 1 => 0 // 0 1 0 => +1 // 0 0 1 => -1 // 0 0 0 => 0 return res; } int diff(int bits) { return countBits(bits ^ tortoise::seekbits); } int progress(int bits) { return numbits - diff(bits); } string measure(int bits) { return "(" + its(progress(bits)) + "/" + its(tortoise::numbits) + ")"; } } namespace dragon { int whichturn; // which turn has the target been set on cell *target; // actually for all Orb of Control void pullback(cell *c) { int maxlen = 1000; while(maxlen-->0) { cell *c2 = c->mov[c->mondir]; mountmove(c, c->mondir, true, c2); c->monst = c2->monst; c->hitpoints = c2->hitpoints; animateMovement(c2, c, LAYER_BIG); c->stuntime = 2; if(c2->mondir == NODIR) { c->mondir = NODIR; c2->monst = moNone; return; } c = c2; } } bool dragbugs = false; cell *findhead(cell *c) { cell *cor = c; int maxlen=1000; findhead: if(maxlen--<0) return c; if(c->monst == moDragonHead) return c; for(int i=0; itype; i++) if(c->mov[i] && isDragon(c->mov[i]->monst) && c->mov[i]->mondir == c->spn(i)) { c = c->mov[i]; goto findhead; } if(!conformal::includeHistory) { printf("dragon bug #3 (%p -> %p)\n", cor, c); dragbugs = true; } c->monst = moDragonHead; return c; } void validate(const char *where) { dragbugs = false; for(int i=0; imonst == moDragonTail) findhead(dcal[i]); if(dragbugs) { printf("DRAGON BUG in %s\n", where); exit(1); } } int bodypart(cell *c, cell *head) { int i = 0, j = 0; int maxlen = 1000; while(maxlen-->0) { if(head == c) i = j; j++; if(head->mondir == NODIR) break; head = head->mov[head->mondir]; } if(i == 0) return 'h'; if(i == 1) return 'l'; if(i == j-2) return '2'; if(i == j-1) return 't'; if(i == 2) return 'w'; return 0; } void kill(cell *c, eMonster who) { int delay = false; kills[moDragonHead]++; int penalty = 0; int maxlen = 1000; while(maxlen-->0) { makeflame(c, 5, false); eMonster m = c->monst; drawFireParticles(c, 16); c->monst = moNone; if(checkOrb(who, itOrbUndeath)) c->monst = moFriendlyGhost; if(checkOrb(who, itOrbStone)) c->wparam = m, c->wall = waPetrified; else if(c->wall == waFire) { if(delay) delay = false; else { if(c->land != laDragon) penalty += 3; if(penalty) penalty--; else { c->item = itDragon; c->landparam = shmup::on ? shmup::curtime : turncount; delay = true; } } } if(c->mondir == NODIR) break; c = c->mov[c->mondir]; } } int totalhp(cell *c) { int total = 0; int maxlen = 1000; while(maxlen-->0) { if(!isDragon(c->monst)) { if(!conformal::includeHistory) printf("dragon bug #4\n"); return total; } total += c->hitpoints; if(c->mondir == NODIR) return total; c = c->mov[c->mondir]; } return total; } #define SWAPBITFIELD(x,y,t) { t bak=x; x=y; y=bak; } void pullfront(cell *c, cell *until) { int maxlen = 1000; static vector allcells; allcells.clear(); while(maxlen-->0) { allcells.push_back(c); // SWAPBITFIELD(c->monst, buffer->monst, eMonster); // SWAPBITFIELD(c->hitpoints, buffer->hitpoints, int); c->stuntime = 2; if(c == until) { for(int i=size(allcells)-2; i>=0; i--) { cell *cmt = allcells[i+1]; cell *cft = allcells[i]; cmt->hitpoints = cft->hitpoints; cmt->monst = cft->monst; cft->monst = moNone; mountmove(cmt, cmt->mondir, true, cft); animateMovement(cft, cmt, LAYER_BIG); } while(c->mondir != NODIR) { c = c->mov[c->mondir]; c->stuntime = 2; } break; } if(c->mondir == NODIR) { printf("dragon bug\n"); break; } c = c->mov[c->mondir]; if(!c) { if(!conformal::includeHistory) printf("dragon bug #2\n"); break; } } } bool move(cell *dt, cell *df) { if(df->monst == moDragonHead) { dt->mondir = neighborId(dt,df); // printf("pull back\n"); pullback(dt); dt->stuntime = 2; return true; } if(df->monst == moDragonTail && df->stuntime == 0) { cell *head = findhead(df); if(df->mondir == NODIR) { df->mondir = neighborId(df,dt); dt->mondir = NODIR; // printf("pull all: head = %p, df=%p, dt=%p\n", head, df, dt); pullfront(head, dt); } else { cell *c2 = df->mov[df->mondir]; if(!c2) return false; int id = neighborId(dt, c2); if(id == -1) return false; dt->mondir = id; df->mondir = neighborId(df, dt); // printf("pull front: head = %p, df=%p, dt=%p\n", head, df, dt); pullfront(head, dt); } return true; } return false; } } namespace sword { int angle[MAXPLAYER]; cell *pos(cell *c, int s) { int t = c->type; s *= 2; s += S42/t; s %= S84; if(s<0) s += S84; s /= (S84/t); return c->mov[s]; } eItem orbof(bool rev) { return rev ? itOrbSword2 : itOrbSword; } int orbcount(bool rev) { return items[orbof(rev)]; } cell *pos(int id, bool rev) { if(!orbcount(rev)) return NULL; return pos(playerpos(id), angle[id] + (rev ? S21 : 0)); } bool at(cell *where, bool noplayer) { if(noplayer) return false; if(!orbcount(0) && !orbcount(1)) return false; for(int i=0; imirror(s1)) return ((s2*S42/c2->type - angle + s1*S42/c1->type) + S21) % S42; else return ((s2*S42/c2->type - s1*S42/c1->type) + S21 + angle) % S42; } void shuffle() { for(int i=0; imonst == moKrakenH) return c; if(c->monst == moKrakenT) return c->mov[c->mondir]; return NULL; } void kill(cell *c, eMonster who) { drawParticles(c, minf[moKrakenH].color, 16); c->monst = moNone; if(checkOrb(who, itOrbUndeath)) c->monst = moFriendlyGhost; if(c->land == laKraken && !c->item) c->item = itKraken; kills[moKrakenH]++; if(checkOrb(who, itOrbStone)) c->wall = waNone; for(int i=0; itype; i++) if(c->mov[i]->monst == moKrakenT) { drawParticles(c, minf[moKrakenT].color, 16); c->mov[i]->monst = moNone; if(checkOrb(who, itOrbStone)) { if(isWatery(c->mov[i])) c->mov[i]->wall = waNone; else c->mov[i]->wall = waPetrified, c->mov[i]->wparam = moKrakenT; } } } int totalhp(cell *c) { int total = 0; for(int i=0; itype; i++) if(c->mov[i]->monst == moKrakenT) total += c->mov[i]->hitpoints; return total; } void trymove(cell *c); void sleep(cell *c) { if(c->monst == moKrakenT) c = c->mov[c->mondir]; c->stuntime = 1; forCellEx(c2, c) c2->stuntime = 1; } void attacks() { bool offboat[MAXPLAYER]; for(int i=0; imonst == moKrakenT && !c->stuntime) forCellEx(c2, c) { bool dboat = false; if(c2->monst && canAttack(c, moKrakenT, c2, c2->monst, AF_ONLY_FBUG)) { attackMonster(c2, AF_ORSTUN | AF_MSG, c->monst); sleep(c); } else for(int i=0; imonst == moKrakenH && !c->stuntime && !isWateryOrBoat(c)) { int qdir = 0; cell *ctab[7]; forCellEx(c2, c) if(isWatery(c2)) ctab[qdir++] = c2; random_shuffle(ctab, ctab+qdir); while(qdir--) trymove(ctab[qdir]); } } } // c is the tentacle which will be the head after the move void trymove(cell *c) { if(pseudohept(c)) return; cell *c2 = c->mov[c->mondir]; if(!isWatery(c)) return; if(againstCurrent(c, c2)) return; forCellIdEx(c3, i, c) { if(c3->monst && c3 != c2 && !(c3->mondir >= 0 && c3->mondir < c3->type && c3->mov[c3->mondir] == c2)) return; if(isPlayerOn(c3)) return; if(sword::at(c3)) return; if(!passable(c3, c, P_FISH | P_MONSTER)) return; } if(c2->stuntime) return; int hpcount[10]; forCellIdEx(c3, i, c2) { hpcount[i] = c3->hitpoints; c3->monst = moNone; } c->monst = moKrakenH; vector > acells; acells.push_back(make_pair(c2, c)); forCellIdEx(c3, i, c) { c3->monst = moKrakenT, c3->mondir = c->spn(i), c3->aitmp = sval; int i0 = (i+c->spn(c->mondir)-c->mondir+99) % c2->type; c3->hitpoints = hpcount[i0]; acells.push_back(make_pair(c2->mov[i0], c3)); if(c3->wall == waBoat) { addMessage(XLAT("%The1 destroys %the2!", moKrakenH, waBoat)); c3->wall = waSea; } if(c3->wall == waStrandedBoat) { addMessage(XLAT("%The1 destroys %the2!", moKrakenH, waBoat)); c3->wall = waNone; } } while(size(acells)) { // bool found = false; for(int i=0; iaitmp = sval; return; } } bool barrierhept(cell *c) { return c->bardir != NOBARRIERS && c->bardir != NODIR; } namespace prairie { using namespace fieldpattern; int getfval(cell *c3) { if(barrierhept(c3)) return btspin(fieldval(c3).first, c3->bardir)+1; return 0; } void spread(cell *c, cell *from) { int rd; c->LHU.fi.flowerdist = 8; c->LHU.fi.walldist = 8; c->LHU.fi.walldist2 = 8; if(torus) { c->LHU.fi.rval = 0; } else if(euclid) { eucoord x, y; decodeMaster(c->master, x, y); c->LHU.fi.rval = (y&15); } else if(sphere) { c->LHU.fi.rval = celldistance(c, cwt.c) + 8 - (purehepta ? 2 : 3); } else { if(!from) { for(int i=0; ifval = fp43.inverses[i]+1; } else if(from && from->land == laPrairie && from->fval) c->fval = from->fval; else { forCellEx(c2, c) if(c2->land == laPrairie && c2->fval) c->fval = c2->fval; if(!c->fval) forCellEx(c2, c) if(!c->fval) c->fval = getfval(c2); if(!c->fval) forCellEx(c2, c) forCellEx(c3, c2) if(!c->fval) c->fval = getfval(c3); if(!c->fval) { int barclose = 0; forCellEx(c2, c) if(barrierhept(c2)) barclose++; forCellEx(c2, c) forCellEx(c3, c2) if(barrierhept(c3)) barclose++; printf("c = %p bc = %d\n", c, barclose); raiseBuggyGeneration(c, "could not set river fval"); } } pair fv = fieldpattern::fieldval(c); fv = fp43.gmul(fv, fp43.inverses[c->fval-1]); rd = fp43.getdist(fv, fp43.distriver); int rl = fp43.getdist(fv, fp43.distriverleft); int rr = fp43.getdist(fv, fp43.distriverright); c->LHU.fi.flowerdist = fp43.getdist(fv, fp43.distflower); c->LHU.fi.walldist = fp43.getdist(fv, fp43.distwall); c->LHU.fi.walldist2 = fp43.getdist(fv, fp43.distwall2); c->LHU.fi.rval = 0; if(rd <= 7 && rl < rr) c->LHU.fi.rval = 8 + rd; if(rd <= 7 && rl > rr) c->LHU.fi.rval = 7 - rd; } if(c->LHU.fi.flowerdist == 0 && c->type != 6) c->item = itOrbSafety; if(c->LHU.fi.walldist == 0) c->wall = waBarrier; if(0) if(c->type == 7) for(int i=0; i<7; i++) { eItem m = fp43.markers[fieldpattern::btspin(c->master->fieldval,i)]; if(m) { if(c->item) c->item = itBuggy2; else c->item = m, c->mondir = i; if(c->wall == waSea) c->wall = waBoat; } } } #define RLOW (sphere?(purehepta?7:6):purehepta?4:2) #define RHIGH (sphere?(purehepta?8:9):purehepta?11:13) bool isriver(cell *c) { return c->land == laPrairie && c->LHU.fi.rval <= RHIGH && c->LHU.fi.rval >= RLOW; } bool mainriver(cell *c) { return c->LHU.fi.rval <= 8 && c->LHU.fi.rval >= 7; } bool nearriver(cell *c) { return c->LHU.fi.rval == RHIGH+1 || c->LHU.fi.rval == RLOW-1; } cell *enter; bool opposite(cell *c) { return (c->LHU.fi.rval ^ enter->LHU.fi.rval) & 8; } bool isleft(cell *c) { return c->LHU.fi.rval & 8; } int towerleft(cell *c) { return c->LHU.fi.rval; } int towerright(cell *c) { return 15^c->LHU.fi.rval; } cell *next(cell *c, int pv=1) { for(int i=0; itype; i++) { cell *c1 = createMov(c, i); cell *c2 = createMov(c, (i+pv+c->type)%c->type); if(c1 && c1->LHU.fi.rval == c->LHU.fi.rval) if(c2 && c2->LHU.fi.rval == c->LHU.fi.rval+1) if(isNeighbor(c1,c2)) return c1; } return NULL; } cell *prev(cell *c) { return next(c, -1); } int beastdist(cell *c, int dir) { cell *cx = c; int n=0; while(true) { if(cx->monst == moHerdBull) return n; cx = next(cx, dir); n++; if(!cx || n >= 1000) return 1000; } } vector beaststogen; void generateBeast(cell *c) { int beastdistance = min(beastdist(c, 1), beastdist(c, -1)); if(hrand(1000) >= 15 * beastdistance + 2 * items[itGreenGrass]) return; c->monst = moHerdBull; cell *c2 = prev(c); if(c2) c->mondir = neighborId(c, c2); } void moveAt(cell *c) { if(eq(c->aitmp, sval)) return; vector whirlline; whirlline.push_back(c); c->aitmp = sval; cell *c2 = prev(c); while(c2 && !eq(c2->aitmp, sval)) { whirlline.push_back(c2), c2->aitmp = sval; c2 = prev(c2); // in sphere/quotient geometries, never break before a bull if((sphere || quotient) && !c2->monst) break; } reverse(whirlline.begin(), whirlline.end()); c2 = next(c); while(c2 && !eq(c2->aitmp, sval)) whirlline.push_back(c2), c2->aitmp = sval, c2 = next(c2); int qty = size(whirlline); // for(int i=0; iaitmp = sval; if(shmup::on) { for(int i=0; icpdist <= 7) { generateBeast(whirlline[i]); break; } } else { c2 = whirlline[qty-1]; if(c2->monst == moHerdBull) c2->monst = moNone; if(!shmup::on) for(int q=qty-2; q>=0; q--) { cell *cp = whirlline[q]; cell *cn = whirlline[q+1]; if(cp->monst == moHerdBull && !cp->stuntime) { forCellEx(c2, cp) { int flags = AF_GETPLAYER | AF_BULL | AF_ORSTUN; if(c2 != cn) flags |= AF_ONLY_FBUG; if(canAttack(c, moHerdBull, c2, c2->monst, flags)) attackMonster(c2, flags | AF_MSG, moHerdBull); } if(!cn->monst && !isPlayerOn(cn) && passable_for(cp->monst, cn, cp, P_DEADLY)) moveMonster(cn, cp); else { playSound(NULL, "hit-axe"+pick123()); beastcrash(cn, cp); cp->monst = moRagingBull; cp->stuntime = 3; cp->mondir = NODIR; } } } if(!sphere && !quotient) generateBeast(whirlline[0]); } } void move() { sval++; for(int i=0; i tchoices; cell *lasttreasure; // vector orbs; bool thisriver(cell *c) { forCellEx(c2, c) if(c2->mpdist < c->mpdist && !isriver(c2)) return false; forCellEx(c2, c) if(c2->mpdist < c->mpdist) return thisriver(c2); return true; } void generateTreasure(cell *c) { // if(nearriver(c) && op if(enter && nearriver(c) && opposite(c) && thisriver(c)) { int hr = hrand(100); if(hr == 0 && items[itGreenGrass] >= 10) { c->item = itOrbBull; // orbs.push_back(c); } else if(hr < 1+PRIZEMUL) { placePrizeOrb(c); // if(c->item) orbs.push_back(c); } else tchoices.push_back(c); } } void treasures() { if(enter && !isriver(cwt.c)) enter = NULL; else if(!enter && isriver(cwt.c)) enter = cwt.c; if(size(tchoices)) { if(lasttreasure && lasttreasure->item == itGreenGrass) { if(celldistance(lasttreasure, cwt.c) >= (purehepta ? 7 : 10)) { lasttreasure->item = itNone; forCellEx(c2, lasttreasure) if(c2->item == itGreenGrass) c2->item = itNone; } else { tchoices.clear(); return; } } if(size(tchoices) < 3) { tchoices.clear(); return; } lasttreasure = tchoices[hrand(size(tchoices))]; lasttreasure->item = itGreenGrass; for(int i=0; iitem = itGreenGrass; tchoices.clear(); } } } namespace ca { ld prob = .2; string carule[8][2]; void init() { // hexagonal variant of Game of Life, as suggested by Wikipedia for(int i=0; i<8; i++) carule[i][0] = "00100000", carule[i][1] = "00011000"; } #if CAP_COMMANDLINE bool readArg() { using namespace arg; if(argis("-caprob")) { shift(); prob = argf(); return true; } if(args()[0] != '-') return false; if(args()[1] != 'c') return false; int livedead = args()[2] - '0'; if(livedead < 0 || livedead > 1) return false; int nei = -1; if(args()[3]) { nei = args()[3] - '0'; if(nei < 0 || nei > 7) return false; if(args()[4]) return false; } shift(); string s = args(); s += "00000000"; if(nei == -1) for(int i=0; i<8; i++) carule[i][livedead] = s; else carule[nei][livedead] = s; return true; } #endif void simulate() { if(cwt.c->land != laCA) return; vector& allcells = currentmap->allcells(); int dcs = size(allcells); bool willlive[dcs]; for(int i=0; iland != laCA) return; int nei = 0, live = 0; forCellEx(c2, c) if(c2->land == laCA) { nei++; if(c2->wall == waFloorA) live++; } int welive = 0; if(c->wall == waFloorA) welive++; willlive[i] = carule[nei][welive][live] == '1'; } for(int i=0; iwall = willlive[i] ? waFloorA : waNone; } } } auto ccm = addHook(clearmemory, 0, [] () { offscreen.clear(); princess::clear(); mirror::mirrors.clear(); clearing::bpdata.clear(); tortoise::emap.clear(); tortoise::babymap.clear(); prairie::lasttreasure = NULL; prairie::enter = NULL; prairie::tchoices.clear(); prairie::beaststogen.clear(); mirror::clearcache(); });