1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2024-12-25 09:30:35 +00:00
hyperrogue/complex.cpp

4234 lines
208 KiB
C++
Raw Normal View History

// Hyperbolic Rogue -- Complex features
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
2016-08-26 09:58:03 +00:00
/** \file complex.cpp
* \brief This file implements the gameplay/generation for the more complex lands and mechanics.
*
* Includes: whirlwind, whirlpool, elec, princess, clearing, mirror, hive, heat + livecaves, etc.
*/
2016-08-26 09:58:03 +00:00
#include "hyper.h"
namespace hr {
2019-08-10 08:57:14 +00:00
EX namespace whirlwind {
2016-08-26 09:58:03 +00:00
EX int fzebra3(cell *c) {
if(arcm::in()) return 0;
2016-08-26 09:58:03 +00:00
if(euclid) {
2019-11-27 00:01:20 +00:00
if(bounded) return 0;
auto co = euc2_coordinates(c);
int y = co.second;
2016-08-26 09:58:03 +00:00
return 1+((((signed short)(y)+int(50000))/3)%3);
}
2017-11-03 19:55:41 +00:00
if(S7 == 5) return getHemisphere(c, 0) > 0 ? 1 : 2;
if(S7 < 5) {
int d = celldistance(currentmap->gamestart(), c);
if(d == 0) return 0;
if(S7 == 4 && d == 3) return 0;
if(S7 == 3 && d == 2 && !ctof(c)) return 0;
return 1;
}
2016-08-26 09:58:03 +00:00
return zebra3(c);
}
EX void switchTreasure(cell *c) {
2016-08-26 09:58:03 +00:00
c->item = itNone;
if(safety) return;
if(hrand(5000) < PT(100 + 2 * (kills[moAirElemental] + kills[moWindCrow]), 200) && notDippingFor(itWindstone)
&& (!shmup::on || getGhostcount() < 2))
2016-08-26 09:58:03 +00:00
c->item = itWindstone;
else if(hrand(5000) < 20*PRIZEMUL)
placeLocalOrbs(c);
}
EX int cat(cell *c) {
2016-08-26 09:58:03 +00:00
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;
EX int dfrom[2], dto[2];
EX int qdirs;
2016-08-26 09:58:03 +00:00
int gdist(int d, int e) { return dirdiff(d-e, where->type); }
EX void calcdirs(cell *c) {
2016-08-26 09:58:03 +00:00
where = c;
int d = cat(c);
qdirs = 0;
if(d == 0) return;
int qdf = 0, qdt = 0;
2020-01-18 15:03:32 +00:00
vector<int> cats(c->type);
2016-08-26 09:58:03 +00:00
for(int i=0; i<c->type; i++)
cats[i] = cat(createMov(c,i));
for(int i=0; i<c->type; 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]));
}
EX int winddir(int d) {
2016-08-26 09:58:03 +00:00
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<cell*>& whirlline, int d) {
again:
2018-06-22 12:47:24 +00:00
cell *at = whirlline[isize(whirlline)-1];
cell *prev = whirlline[isize(whirlline)-2];
2016-08-26 09:58:03 +00:00
for(int i=0; i<at->type; i++)
if(at->move(i) && (euclid || at->move(i)->master->alt) && celldistAlt(at->move(i)) == d && at->move(i) != prev) {
whirlline.push_back(at->move(i));
2016-08-26 09:58:03 +00:00
goto again;
}
}
2018-06-28 11:35:03 +00:00
void moveAt(cell *c, manual_celllister& cl) {
if(cl.listed(c)) return;
2016-08-26 09:58:03 +00:00
calcdirs(c);
if(qdirs != 1) return;
vector<cell*> whirlline;
whirlline.push_back(c);
cell *prev = c;
cell *c2 = c->move(dfrom[0]);
2016-08-26 09:58:03 +00:00
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->move(dfrom[0]);
else if(c2->move(dto[0]) == prev)
c2 = c2->move(dfrom[1]);
2016-08-26 09:58:03 +00:00
else
c2 = c2->move(dfrom[0]);
2016-08-26 09:58:03 +00:00
prev = cc2;
}
2018-06-22 12:47:24 +00:00
int z = isize(whirlline);
2016-08-26 09:58:03 +00:00
// printf("Cycle built from %p, length = %d\n", c, z);
for(int i=0; i<z; i++) {
// printf("%d%c", whirlline[i]->mpdist, whirlline[i]->item ? '*' : ' ');
cl.add(whirlline[i]);
2016-08-26 09:58:03 +00:00
if(whirlline[i]->mpdist == BARLEV)
switchTreasure(whirlline[i]);
}
for(int i=0; i<z-1; i++) {
moveItem(whirlline[i], whirlline[i+1], true);
2017-03-23 10:53:57 +00:00
if(whirlline[i]->item)
2019-11-22 17:48:51 +00:00
animateMovement(match(whirlline[i+1], whirlline[i]), LAYER_BOAT);
2016-08-26 09:58:03 +00:00
}
for(int i=0; i<z; i++)
pickupMovedItems(whirlline[i]);
2016-08-26 09:58:03 +00:00
}
EX void move() {
2018-06-28 11:35:03 +00:00
manual_celllister cl;
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(dcal); i++) {
2016-08-26 09:58:03 +00:00
cell *c = dcal[i];
moveAt(c, cl);
2016-08-26 09:58:03 +00:00
}
// Keys and Orbs of Yendor always move
using namespace yendor;
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(yi); i++) {
moveAt(yi[i].path[0], cl);
moveAt(yi[i].key(), cl);
2016-08-26 09:58:03 +00:00
}
}
EX cell *jumpFromWhereTo(cell *c, bool player) {
2016-08-26 09:58:03 +00:00
for(int i=0; i<2; i++) {
calcdirs(c);
if(qdirs != 1) return NULL;
cell *c2 = c->move(dfrom[0]);
2016-08-26 09:58:03 +00:00
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;
}
EX cell *jumpDestination(cell *c) {
2016-08-26 09:58:03 +00:00
for(int i=0; i<2; i++) {
calcdirs(c);
if(qdirs != 1) return NULL;
c = c->move(dto[0]);
2016-08-26 09:58:03 +00:00
}
calcdirs(c);
if(qdirs != 1) return NULL;
return c;
}
2019-08-10 08:57:14 +00:00
EX }
2016-08-26 09:58:03 +00:00
2019-08-10 08:57:14 +00:00
EX namespace elec {
2016-08-26 09:58:03 +00:00
EX bool havecharge, haveelec, havethunder;
EX bool afterOrb; // extra charge from the Orb of Lightning
2016-08-26 09:58:03 +00:00
#if HDR
2016-08-26 09:58:03 +00:00
enum eCharge {
ecCharged, ecGrounded, ecIsolator, ecConductor
};
#endif
2016-08-26 09:58:03 +00:00
bool conduct(eCharge cf, eCharge ct) {
if(ct == ecIsolator) return false;
if(ct == ecConductor) return true;
return ct != cf;
}
EX eCharge getCharge(cell *c) {
2016-08-26 09:58:03 +00:00
bool ao = afterOrb && c->ligon;
2020-02-26 21:30:11 +00:00
/* not yet generated */
if(c->land == laNone) return ecIsolator;
2016-08-26 09:58:03 +00:00
if(c->wall == waCharged) return ecCharged;
if(c->wall == waSea || c->wall == waGrounded) return ecGrounded;
if(c->wall == waSandstone || c->wall == waDeadTroll ||
2017-03-23 10:53:57 +00:00
c->wall == waDeadTroll2 ||
2020-02-26 00:34:30 +00:00
among(c->wall, waBigTree, waSmallTree, waExplosiveBarrel, waRed1, waRed2, waRed3) ||
2016-08-26 09:58:03 +00:00
c->wall == waVinePlant ||
c->wall == waMetal || isAlchAny(c))
2020-02-26 00:22:26 +00:00
return isElectricLand(c) ? ecConductor : ecGrounded;
2016-08-26 09:58:03 +00:00
if(c->wall == waBarrier)
return ecIsolator;
if(c->wall == waChasm)
return ecIsolator;
2020-02-29 16:58:59 +00:00
if(shmup::on ? isPlayerOn(c) : (isPlayerOn(c) || (items[itOrbEmpathy] && isFriendly(c)))) {
2016-08-26 09:58:03 +00:00
if(items[itOrbShield]) return ecIsolator;
2017-03-23 10:53:57 +00:00
if(afterOrb) return ecIsolator;
2020-02-26 00:22:26 +00:00
if(!items[itOrbAether]) return isElectricLand(c) ? ecConductor : ecGrounded;
2016-08-26 09:58:03 +00:00
}
// if(c->monst && stalemate::moveto) printf("%p: isKilled = %d\n", c, stalemate::isKilled(c));
2020-02-29 16:58:59 +00:00
else if(c->monst
&& c->monst != moGhost && c->monst != moIvyDead && c->monst != moIvyNext
2016-08-26 09:58:03 +00:00
&& !(isDragon(c->monst) && !c->hitpoints)
)
2020-02-26 00:22:26 +00:00
return isElectricLand(c) ? (ao ? ecCharged : ecConductor) : ecGrounded;
2016-08-26 09:58:03 +00:00
2020-02-26 00:22:26 +00:00
if(!isElectricLand(c))
2016-08-26 09:58:03 +00:00
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<chargedata> charges;
vector<pair<int, int> > xstack;
vector<cell*> chargecells;
bool hasdata(cell *c) {
return c->listindex >= 0 && c->listindex < isize(charges) && charges[c->listindex].c == c;
2016-08-26 09:58:03 +00:00
}
void connect(int from, cell *c) {
if(hasdata(c)) {
// seen again: set the lowlink
if(!charges[c->listindex].instack) return;
// printf("edge %d-%d\n", from, c->listindex);
if(c->listindex < charges[from].lowlink)
charges[from].lowlink = c->listindex;
2016-08-26 09:58:03 +00:00
}
else {
2018-06-22 12:47:24 +00:00
int id = isize(charges);
2016-08-26 09:58:03 +00:00
charges.push_back(chargedata());
{chargedata& ch(charges[id]);
ch.c = c; ch.otmp = c->listindex; ch.lowlink = id; c->listindex = id;
2016-08-26 09:58:03 +00:00
ch.instack = true; ch.fire = false;
}
2016-08-26 09:58:03 +00:00
// 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));
charges[id].lowlink = 0;
2016-08-26 09:58:03 +00:00
}
else if(chh == ecCharged) {
xstack.push_back(make_pair(id, 1));
if(from != 1) charges[id].lowlink = 1;
2016-08-26 09:58:03 +00:00
}
for(cell *c2: adj_minefield_cells(c)) {
if(c2->listindex == from) continue;
2016-08-26 09:58:03 +00:00
eCharge ct = getCharge(c2);
if(conduct(chh, ct))
connect(id, c2);
}
// printf("lowlink of %d [%s] = %d\n", id, dnameof(c->wall), ch.lowlink);
2018-01-13 16:09:55 +00:00
if(charges[id].lowlink < charges[from].lowlink)
charges[from].lowlink = charges[id].lowlink;
2016-08-26 09:58:03 +00:00
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");
}
charges[id].instack = false;
2016-08-26 09:58:03 +00:00
}
}
void affect(cell *c) {
c->ligon = true;
if(c->monst) {
if(c->monst == moMetalBeast2 && !c->item)
c->item = itFulgurite;
2017-03-23 10:53:57 +00:00
killMonster(c, moLightningBolt);
2016-08-26 09:58:03 +00:00
}
if(isPlayerOn(c)) {
killThePlayerAt(moLightningBolt, c, 0);
}
2020-02-26 00:34:30 +00:00
if(c->wall == waSandstone) {
c->wall = waNone, drawParticles(c, winf[waSandstone].color, 16);
if(c->land == laStorms)
c->item = itFulgurite;
}
if(c->wall == waRed1) c->wall = waNone;
if(c->wall == waRed2) c->wall = waRed1;
if(c->wall == waRed3) c->wall = waRed2;
2016-08-26 09:58:03 +00:00
if(c->wall == waDeadTroll) c->wall = waCavefloor;
2020-02-26 00:34:30 +00:00
if(c->wall == waBigTree || c->wall == waSmallTree)
makeflame(c, 10, false);
if(c->wall == waExplosiveBarrel)
explodeBarrel(c);
2016-08-26 09:58:03 +00:00
if(c->wall == waDeadTroll2 || isAlchAny(c) || c->wall == waVinePlant)
2017-03-23 10:53:57 +00:00
drawParticles(c, winf[c->wall].color, 16),
2016-08-26 09:58:03 +00:00
c->wall = waNone;
/* destroy charged walls on the border */
if(c->wall == waCharged) forCellEx(c1, c) if(c1->wall == waGrounded)
c->wall = waNone;
2016-08-26 09:58:03 +00:00
/* if(c->wall == waCharged)
c->wall = waMetal; */
}
2018-06-28 11:35:03 +00:00
void listChargedCells(cell *c, manual_celllister& cl, eCharge last = ecConductor) {
if(cl.listed(c)) return;
2016-08-26 09:58:03 +00:00
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) cl.add(c);
2016-08-26 09:58:03 +00:00
if(!conduct(last, here)) return;
if(here == ecCharged) chargecells.push_back(c);
cl.add(c);
2016-08-26 09:58:03 +00:00
for(int i=0; i<c->type; i++) {
cell *c2 = c->move(i);
if(c2) listChargedCells(c2, cl, here);
2016-08-26 09:58:03 +00:00
}
}
EX void init() {
2016-08-26 09:58:03 +00:00
chargecells.clear();
2017-03-23 10:53:57 +00:00
if(!haveelec && !afterOrb) return;
2018-06-28 11:35:03 +00:00
if(1) {
manual_celllister cl;
for(int i=0; i<isize(dcal); i++) listChargedCells(dcal[i], cl);
}
2016-08-26 09:58:03 +00:00
charges.resize(2);
charges[0].lowlink = 0; charges[1].lowlink = 1;
if(!havecharge) return;
xstack.clear();
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(chargecells); i++)
2016-08-26 09:58:03 +00:00
connect(1, chargecells[i]);
havethunder = charges[1].lowlink == 0;
if(havethunder) {
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(xstack); i++) {
2016-08-26 09:58:03 +00:00
int k = xstack[i].first;
int l = xstack[i].second;
// printf("connected %d-%d\n", k, l);
charges[k].fire = true;
charges[l].fire = true;
}
}
}
void fire() {
if(havethunder) {
addMessage(XLAT("There is a flash of thunder!"));
2017-03-23 10:53:57 +00:00
playSound(NULL, "storm");
2016-08-26 09:58:03 +00:00
drawLightning();
2018-06-22 12:47:24 +00:00
for(int i=2; i<isize(charges); i++) if(charges[i].fire)
2016-08-26 09:58:03 +00:00
affect(charges[i].c);
}
}
EX void cleanup() {
2018-06-22 12:47:24 +00:00
for(int i=2; i<isize(charges); i++)
charges[i].c->listindex = charges[i].otmp;
2016-08-26 09:58:03 +00:00
charges.resize(0);
}
void draw(cell *c, eCharge what) {
if(c->ligon) return;
c->ligon = true;
for(int i=0; i<c->type; i++) {
cell *c2 = c->move(i);
2016-08-26 09:58:03 +00:00
if(!c2) continue;
eCharge ch = getCharge(c2);
if(conduct(what, ch))
draw(c2, ch);
}
}
void drawcharges() {
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(dcal); i++)
2016-08-26 09:58:03 +00:00
if(getCharge(dcal[i]) == ecCharged)
draw(dcal[i], ecCharged);
}
EX bool affected(cell *c) {
if(c->listindex >= 0 && c->listindex < isize(charges) && charges[c->listindex].c == c)
return charges[c->listindex].fire;
2016-08-26 09:58:03 +00:00
return false;
}
#if HDR
2016-08-26 09:58:03 +00:00
struct builder {
builder() { init(); }
~builder() { cleanup(); }
};
#endif
2016-08-26 09:58:03 +00:00
EX void act() {
2016-08-26 09:58:03 +00:00
int k = tkills();
2017-03-23 10:53:57 +00:00
for(int i=0; i<numplayers(); i++)
2020-02-26 00:22:26 +00:00
if(multi::playerActive(i) && isElectricLand(playerpos(i)) && !afterOrb)
2017-03-23 10:53:57 +00:00
markOrb(itOrbShield), markOrb(itOrbAether);
2016-08-26 09:58:03 +00:00
builder b;
fire();
if(!afterOrb)
achievement_count("ELEC", tkills() - k, 0);
}
// 0 = no close escape, 1 = close escape, 2 = message already shown
2019-08-10 08:57:14 +00:00
EX int lightningfast;
EX void checklightningfast() {
2020-02-29 16:58:59 +00:00
changes.value_keep(lightningfast);
2016-08-26 09:58:03 +00:00
if(lightningfast == 1) {
addMessage(XLAT("Wow! That was close."));
lightningfast = 2;
}
if(lightningfast > 1) return;
builder b;
2017-03-23 10:53:57 +00:00
for(int i=0; i<numplayers(); i++)
if(multi::playerActive(i) && elec::affected(playerpos(i)))
lightningfast = 1;
2016-08-26 09:58:03 +00:00
}
2019-08-10 08:57:14 +00:00
EX }
2016-08-26 09:58:03 +00:00
2019-08-09 23:31:44 +00:00
EX namespace princess {
#if HDR
#define OUT_OF_PRISON 200
#define OUT_OF_PALACE 250
#define PRADIUS0 (141)
#define PRADIUS1 (150)
struct info {
int id; // id of this info
cell *prison; // where was the Princess locked
heptagon *alt; // alt of the prison
int bestdist; // best dist achieved
int bestnear; // best dist achieved, by the player
int value; // number of Rugs at 120
cell *princess; // where is the Princess currently
};
#endif
2016-08-26 09:58:03 +00:00
2019-12-08 09:59:09 +00:00
EX gp::loc coords() { return gp::loc(39, 21); }
2019-08-09 23:31:44 +00:00
EX bool generating = false;
EX bool challenge = false;
EX bool saved = false;
EX bool everSaved = false;
2016-08-26 09:58:03 +00:00
2019-08-09 23:31:44 +00:00
EX bool forceVizier = false;
EX bool forceMouse = false;
EX bool gotoPrincess = false;
EX bool nodungeon = false;
EX bool squeaked = false;
2016-08-26 09:58:03 +00:00
EX int saveHP = 0;
EX int saveArmedHP = 0;
2016-08-26 09:58:03 +00:00
EX int reviveAt;
2017-03-23 10:53:57 +00:00
2016-08-26 09:58:03 +00:00
vector<info*> infos;
void assign(info *i) {
if(i->alt) i->alt->emeraldval = i->id;
}
2019-08-09 23:31:44 +00:00
EX int newInfo(cell *c) {
2016-08-26 09:58:03 +00:00
info *i = new info;
i->prison = c;
i->princess = c;
i->alt = c->master->alt;
2018-06-22 12:47:24 +00:00
i->id = isize(infos);
2016-08-26 09:58:03 +00:00
i->bestdist = 0;
i->bestnear = OUT_OF_PRISON;
infos.push_back(i);
assign(i);
2017-07-16 21:00:55 +00:00
return i->id;
2016-08-26 09:58:03 +00:00
}
EX void newFakeInfo(cell *c) {
2016-08-26 09:58:03 +00:00
info *i = new info;
i->prison = NULL;
i->princess = c;
i->alt = NULL;
2018-06-22 12:47:24 +00:00
i->id = isize(infos);
2017-07-16 21:00:55 +00:00
i->bestdist = items[itSavedPrincess] ? OUT_OF_PALACE : OUT_OF_PRISON;
2016-08-26 09:58:03 +00:00
i->bestnear = 0;
infos.push_back(i);
assign(i);
}
EX info *getPrisonInfo(cell *c) {
2017-10-30 21:47:07 +00:00
if(euclid || quotient || sphere) return NULL;
2016-08-26 09:58:03 +00:00
if(c->land != laPalace) return NULL;
if(!c->master->alt) return NULL;
2017-07-16 21:00:55 +00:00
int ev = c->master->alt->alt->emeraldval; // NEWYEARFIX
2018-06-22 12:47:24 +00:00
if(ev < 0 || ev >= isize(infos)) return NULL;
2016-08-26 09:58:03 +00:00
if(infos[ev]->alt != c->master->alt->alt) return NULL;
return infos[ev];
}
EX info *getPrincessInfo(cell *c) {
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(infos); i++) if(infos[i]->princess == c) {
2016-08-26 09:58:03 +00:00
while(i) {
infos[i]->id = i-1; assign(infos[i]);
infos[i-1]->id = i; assign(infos[i-1]);
2017-07-16 21:00:55 +00:00
swap(infos[i], infos[i-1]);
2016-08-26 09:58:03 +00:00
i--;
}
return infos[i];
}
return NULL;
}
EX int dist(cell *c) {
2017-07-16 21:00:55 +00:00
if(c->land != laPalace && c->land != laDungeon) return OUT_OF_PALACE;
2019-11-27 00:01:20 +00:00
else if(quotient || sphere) return OUT_OF_PRISON;
2016-08-26 09:58:03 +00:00
else if(euclid) return celldistAlt(c);
else if(!c->master->alt) return OUT_OF_PRISON;
else return celldistAlt(c);
}
void clear() {
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(infos); i++) delete infos[i];
2016-08-26 09:58:03 +00:00
infos.clear();
}
bool bringBackAt(cell *c) {
if(!c) return false;
if(!passable(c, NULL, 0)) return false;
c->monst = moPrincessArmed;
c->stuntime = 0;
c->hitpoints = palaceHP();
drawFlash(c);
2017-03-23 10:53:57 +00:00
playSound(c, princessgender() ? "heal-princess" : "heal-prince");
2016-08-26 09:58:03 +00:00
info *inf = NULL;
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(infos); i++) {
2017-07-16 21:00:55 +00:00
if(infos[i]->princess && infos[i]->bestdist == OUT_OF_PALACE && isPrincess(infos[i]->princess->monst))
2016-08-26 09:58:03 +00:00
inf = infos[i];
2017-07-16 21:00:55 +00:00
}
2016-08-26 09:58:03 +00:00
if(inf) { inf->princess->monst = moNone; inf->princess = c; }
else newFakeInfo(c);
return true;
}
EX void bringBack() {
if(bringBackAt(cwt.peek())) return;
2018-06-22 12:47:24 +00:00
for(int i=1; i<isize(dcal); i++)
2016-08-26 09:58:03 +00:00
if(bringBackAt(dcal[i])) return;
}
void setdist(info *i, int newdist) {
2020-03-01 13:09:24 +00:00
changes.value_keep(*i);
2016-08-26 09:58:03 +00:00
if(newdist < ALTDIST_ERROR && newdist > i->bestdist) {
i->bestdist = newdist;
// printf("Improved dist to %d\n", newdist);
if(newdist == OUT_OF_PALACE) {
if(!princess::saved)
2017-07-22 23:33:27 +00:00
#if CAP_INV
2017-07-16 21:00:55 +00:00
if(!inv::on || !inv::usedForbidden)
#endif
achievement_gain_once("PRINCESS1");
2016-08-26 09:58:03 +00:00
princess::saved = true;
princess::everSaved = true;
2017-07-12 16:03:53 +00:00
if(inv::on && !princess::reviveAt)
princess::reviveAt = gold(NO_LOVE);
2016-08-26 09:58:03 +00:00
items[itSavedPrincess]++;
}
if(newdist == OUT_OF_PRISON && princess::challenge) {
addMessage(XLAT("Congratulations! Your score is %1.", its(i->value)));
achievement_gain_once("PRINCESS2");
2016-08-26 09:58:03 +00:00
if(!cheater) achievement_score(36, i->value);
LATE( showMissionScreen(); )
2016-08-26 09:58:03 +00:00
}
}
2017-07-16 21:00:55 +00:00
if(i->princess->land == laDungeon && !saved && !nodungeon) {
addMessage(XLAT("%The1 says, \"not this place, it looks even worse...\"", moPrincess));
nodungeon = true;
}
2016-08-26 09:58:03 +00:00
}
EX void save(cell *princess) {
2016-08-26 09:58:03 +00:00
if(euclid) return;
princess::info *i = princess::getPrincessInfo(princess);
2020-03-01 13:09:24 +00:00
if(!i || i->bestdist <= 3) changes.ccell(princess), princess->monst = moNone;
2017-07-16 21:00:55 +00:00
else if(i) setdist(i, OUT_OF_PRISON);
2016-08-26 09:58:03 +00:00
}
EX void move(const movei& mi) {
auto& cf = mi.s;
auto& ct = mi.t;
2016-08-26 09:58:03 +00:00
if(euclid) return;
princess::info *i = princess::getPrincessInfo(cf);
if(!i) {
// note: OK if mapediting or loading
2017-07-16 21:00:55 +00:00
printf("Warning: unknown princess\n");
if(!cheater)
2016-08-26 09:58:03 +00:00
addMessage("Warning: unknown princess (that's a bug, please report)");
}
else {
2020-03-01 13:09:24 +00:00
changes.value_keep(*i);
2016-08-26 09:58:03 +00:00
i->princess = ct;
setdist(i, dist(ct));
// printf("newdist = %d (vs %d)\n", newdist, i->bestdist);
}
}
EX void mouseSqueak(cell *c) {
2016-08-26 09:58:03 +00:00
eMonster m = c->monst;
info *i = getPrisonInfo(c);
int d = dist(c);
2017-03-23 10:53:57 +00:00
playSound(c, "mousesqueak", 40);
2016-08-26 09:58:03 +00:00
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));
}
EX void line(cell *c) {
2016-08-26 09:58:03 +00:00
int d = (euclid || c->master->alt) ? celldistAlt(c) : 200;
eMonster m = c->monst;
static int msgid = 0;
2020-02-29 16:58:59 +00:00
changes.value_keep(msgid);
2016-08-26 09:58:03 +00:00
2017-03-23 10:53:57 +00:00
playSound(c, princessgender() ? "speak-princess" : "speak-prince");
2016-08-26 09:58:03 +00:00
retry:
2017-08-06 12:50:16 +00:00
if(msgid >= 127) msgid = 0;
2016-08-26 09:58:03 +00:00
2017-07-16 21:00:55 +00:00
bool inpalace = c->land == laPalace || c->land == laDungeon;
if(msgid == 0 && d < 20 && inpalace) {
2016-08-26 09:58:03 +00:00
addMessage(XLAT("%The1 kisses you, and begs you to bring %him1 away from here.", m));
}
2017-07-24 22:21:36 +00:00
else if(msgid == 1 && d >= 20 && inpalace && !peace::on) {
2016-08-26 09:58:03 +00:00
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));
}
2017-07-16 21:00:55 +00:00
else if(msgid == 2 && d >= 70 && inpalace) {
2016-08-26 09:58:03 +00:00
addMessage(XLAT("\"Bring me out of here please!\"", m));
}
2017-07-16 21:00:55 +00:00
else if(msgid == 3 && !inpalace) {
2016-08-26 09:58:03 +00:00
addMessage(XLAT("%The1 kisses you, and thanks you for saving %him1.", m));
}
2017-07-24 22:21:36 +00:00
else if(msgid == 4 && !inpalace && m == moPrincess && !peace::on) {
2016-08-26 09:58:03 +00:00
addMessage(XLAT("\"I have been trained to fight with a Hypersian scimitar, you know?\"", m));
}
2017-08-06 12:50:16 +00:00
else if(msgid == 16 && !inpalace) {
2016-08-26 09:58:03 +00:00
addMessage(XLAT("\"I would love to come to your world with you!\"", m));
}
2017-08-06 12:50:16 +00:00
else if(msgid == 20 && !inpalace) {
addMessage(XLAT("\"I do not like butterflies. They are treacherous.\"", m));
}
else if(msgid == 32 && !inpalace) {
2016-08-26 09:58:03 +00:00
addMessage(XLAT("\"Straight lines stay close to each other forever, this is so romantic!\"", m));
}
2017-08-06 12:50:16 +00:00
else if(msgid == 40 && !inpalace) {
addMessage(XLAT("\"I hate roses.\"", m));
}
else if(msgid == 48 && !inpalace) {
2016-08-26 09:58:03 +00:00
addMessage(XLAT("\"Maps... Just like the world, but smaller... how is that even possible?!\"", m));
}
2017-08-06 12:50:16 +00:00
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 == 74 && !inpalace) {
addMessage(XLAT("\"Did you know that the Cultists are relatives of the Desert Men?\"", m));
}
2017-08-06 12:50:16 +00:00
else if(msgid == 80) {
addMessage(XLAT("\"Are there Temples of Cthulhu in your world? Why not?\"", m));
}
2016-08-26 09:58:03 +00:00
else {
msgid++; goto retry;
}
msgid++;
}
EX void playernear(cell *c) {
2016-08-26 09:58:03 +00:00
info *i = getPrisonInfo(c);
int d = dist(c);
// if(i) printf("d=%d bn=%d\n", d, i->bestnear);
if(i && d < i->bestnear) {
2020-03-01 13:09:24 +00:00
changes.value_keep(*i);
2016-08-26 09:58:03 +00:00
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;
}
}
2019-08-09 23:31:44 +00:00
EX }
2016-08-26 09:58:03 +00:00
2019-08-09 23:31:44 +00:00
EX namespace clearing {
#if HDR
struct clearingdata {
cell *root;
int dist;
bool buggy;
clearingdata() { root = nullptr; }
2019-08-09 23:31:44 +00:00
};
#endif
2016-08-26 09:58:03 +00:00
2019-08-09 23:31:44 +00:00
EX std::map<heptagon*, clearingdata> bpdata;
2016-08-26 09:58:03 +00:00
EX cell *current_root;
2018-04-14 08:24:02 +00:00
EX void new_root() {
2018-04-14 08:24:02 +00:00
if(!current_root || current_root->monst != moMutant) {
auto& ac = currentmap->allcells();
int iter = 0;
while(!current_root || pseudohept(current_root) || current_root->cpdist < 3) {
if(iter++ > 100) return;
2018-06-22 12:47:24 +00:00
current_root = ac[hrand(isize(ac))];
2018-04-14 08:24:02 +00:00
}
current_root->monst = moMutant;
current_root->mondir = NODIR;
current_root->stuntime = (mutantphase + 1) & 15;
}
}
2016-08-26 09:58:03 +00:00
int plantdir(cell *c) {
2017-10-30 21:47:07 +00:00
if(!quotient) {
currentmap->generateAlts(c->master);
2017-10-30 21:47:07 +00:00
for(int i=0; i<S7; i++)
currentmap->generateAlts(c->master->move(i));
2017-10-30 21:47:07 +00:00
}
2016-08-26 09:58:03 +00:00
int d = celldistAlt(c);
if(PURE) {
2017-10-30 21:47:07 +00:00
for(int i=0; i<S7; i++) {
2016-08-26 09:58:03 +00:00
cell *c2 = createMov(c, i);
if(!pseudohept(c2) && celldistAlt(c2) == d-1)
return i;
}
2017-10-30 21:47:07 +00:00
for(int i=0; i<S7; i++) {
2016-08-26 09:58:03 +00:00
cell *c2 = createMov(c, i);
if(celldistAlt(c2) == d-1)
2017-10-30 21:47:07 +00:00
return (i+1) % S7;
2016-08-26 09:58:03 +00:00
}
}
2017-10-30 21:47:07 +00:00
for(int i=1; i<S6; i+=2) {
2016-08-26 09:58:03 +00:00
cell *c2 = createMov(c, i);
if(celldistAlt(c2) == d-1)
return i;
}
2017-10-30 21:47:07 +00:00
int quseful = 0, tuseful = 0, tuseful2 = 0;
for(int i=1; i<S6; i+=2) {
cell *c2 = c->move(i);
2016-08-26 09:58:03 +00:00
if(celldistAlt(c2) == d) {
bool useful = false;
2017-10-30 21:47:07 +00:00
for(int j=1; j<S6; j++) {
2016-08-26 09:58:03 +00:00
cell *c3 = createMov(c2, j);
if(celldistAlt(c3) == d-1)
useful = true;
}
2017-10-30 21:47:07 +00:00
if(useful) quseful++, tuseful += (1<<i), tuseful2 = i;
2016-08-26 09:58:03 +00:00
}
}
2017-10-30 21:47:07 +00:00
if(quseful == 1) return tuseful2;
2016-08-26 09:58:03 +00:00
if(quseful == 2) {
int i;
2017-10-30 21:47:07 +00:00
if(tuseful == (1<<3)+(1<<5)) i = 3;
if(tuseful == (1<<5)+(1<<1)) i = 5;
if(tuseful == (1<<1)+(1<<3)) i = 1;
if(tuseful == (1<<5)+(1<<7)) i = 5;
if(tuseful == (1<<7)+(1<<1)) i = 7;
if((d & 7) < 4) i = (i+2) % S6;
2016-08-26 09:58:03 +00:00
return i;
}
printf("error in plantdir\n");
return 1;
}
vector<cell*> onpath;
vector<int> pdir;
vector<cell*> rpath;
2019-08-10 08:57:14 +00:00
EX void generate(cell *c) {
2018-04-14 08:24:02 +00:00
if(sphere) return;
if(NONSTDVAR) return;
2018-04-14 08:24:02 +00:00
if(quotient) return;
2016-08-26 09:58:03 +00:00
if(euclid) {
2019-11-27 00:01:20 +00:00
if(quotient) return; // fix cylinder
2016-08-26 09:58:03 +00:00
if(pseudohept(c)) return;
c->monst = moMutant;
2017-06-09 01:41:33 +00:00
auto co = euc2_coordinates(c);
int x = co.first, y = co.second;
2016-08-26 09:58:03 +00:00
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) currentmap->generateAlts(c->master);
2016-08-26 09:58:03 +00:00
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; bd.buggy = false; }
if(bd.buggy) return;
2016-08-26 09:58:03 +00:00
onpath.clear(); pdir.clear(); rpath.clear();
int steps = 0;
int ds;
2017-10-30 21:47:07 +00:00
int stepcount = 0;
2016-08-26 09:58:03 +00:00
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)) {
2017-10-30 21:47:07 +00:00
stepcount++;
if(stepcount > iteration_limit) {
2017-10-30 21:47:07 +00:00
printf("buggy #1\n");
bd.buggy = true;
2017-10-30 21:47:07 +00:00
return;
}
2016-08-26 09:58:03 +00:00
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->move(d));
c = c->move(d);
2016-08-26 09:58:03 +00:00
}
else {
bd.dist--;
if(bd.dist < -iteration_limit) {
2016-08-26 09:58:03 +00:00
for(int i=0; i<steps; i++)
onpath[i]->item = itBuggy;
for(int i=0; i<(int) rpath.size(); i++)
rpath[i]->item = itBuggy;
printf("buggy #2\n");
bd.buggy = true;
2016-08-26 09:58:03 +00:00
return;
}
rpath.push_back(bd.root);
// printf("r [%4d] %p -> %p\n", celldistAlt(bd.root), bd.root, bd.root->move(plantdir(bd.root)));
2017-10-30 21:47:07 +00:00
bd.root = createMov(bd.root, plantdir(bd.root));
2016-08-26 09:58:03 +00:00
}
}
// 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);
}
2019-06-13 14:22:35 +00:00
c->stuntime = ds & 15;
2016-08-26 09:58:03 +00:00
}
if(c->mpdist <= 7 && c->monst != moMutant)
break;
steps--; ds++;
}
}
2020-03-01 13:37:48 +00:00
/** cells with the same celltype are likely to have the same number of descendant leaves */
2019-12-25 10:53:50 +00:00
typedef tuple<int, int, int, int> celltype;
2020-03-01 13:37:48 +00:00
/** stats about the number of descendant leaves for each celltype */
2019-12-25 10:53:50 +00:00
map<celltype, pair<bignum, int> > stats;
2020-03-01 13:37:48 +00:00
/** the total number of leaves killed, approximated from the actual numbers and Clearing structure */
2019-12-25 10:53:50 +00:00
EX bignum imputed;
2020-03-01 13:37:48 +00:00
/** the total number of leaves killed directly */
2019-12-25 10:53:50 +00:00
EX int direct;
map<cell*, pair<bignum, int> > score;
celltype get_celltype(cell *c) {
cell *c1 = c;
if(c->mondir < c->type)
c1 = c->move(c->mondir);
return make_tuple(
celldistAlt(c), type_in(expansion, c, celldistAlt),
celldistAlt(c1), type_in(expansion, c1, celldistAlt)
);
}
EX void imput(cell *c) {
2019-12-26 08:40:16 +00:00
if(bounded) return;
2019-12-25 10:53:50 +00:00
if(score.count(c)) return;
2020-02-29 16:58:59 +00:00
changes.map_value(score, c);
2019-12-25 10:53:50 +00:00
auto& is = score[c];
celltype t = get_celltype(c);
2020-02-29 16:58:59 +00:00
changes.map_value(stats, t);
2019-12-25 10:53:50 +00:00
auto& stat = stats[t];
is.second = c->mondir;
if(c->mpdist <= 6) {
is.first = 1;
forCellEx(c2, c) if(score.count(c2) && c2->move(score[c2].second) == c)
is.first += score[c2].first;
stat.first += is.first;
stat.second++;
}
else {
is.first = stat.second ? stat.first.randomized_div(stat.second) : bignum(1);
imputed += is.first;
}
}
2019-08-09 23:31:44 +00:00
EX }
2016-08-26 09:58:03 +00:00
2019-08-10 08:57:14 +00:00
EX namespace whirlpool {
2016-08-26 09:58:03 +00:00
EX bool escaped = false; // escaped the Whirlpool?
2016-08-26 09:58:03 +00:00
// next == +1 -> next
// next == -1 -> prev
EX cell *get(cell *c, int next) {
2016-08-26 09:58:03 +00:00
int i = 0;
2018-08-09 17:28:53 +00:00
if(!eubinary && !c->master->alt) return NULL;
2016-08-26 09:58:03 +00:00
int d = celldistAlt(c);
int d2;
while(true) {
if(i == c->type) return NULL;
if(c->move(i) && (d2 = celldistAlt(c->move(i))) != d)
2016-08-26 09:58:03 +00:00
break;
i++;
}
if(i == c->type) return NULL;
if(d>d2) next = -next;
for(int j=1; j<c->type; j++) {
cell *c2 = c->modmove(i+next*j);
2016-08-26 09:58:03 +00:00
if(celldistAlt(c2) == d) return c2;
}
return NULL;
}
void build(vector<cell*>& whirlline, int d) {
again:
2018-06-22 12:47:24 +00:00
cell *at = whirlline[isize(whirlline)-1];
cell *prev = whirlline[isize(whirlline)-2];
2016-08-26 09:58:03 +00:00
for(int i=0; i<at->type; i++)
if(at->move(i) && (eubinary || at->move(i)->master->alt) && celldistAlt(at->move(i)) == d && at->move(i) != prev) {
if(at->move(i) == whirlline[0]) return; // loops in weird geometries?
if(at->move(i) == whirlline[isize(whirlline)/2]) return; // even weirder geometry?
whirlline.push_back(at->move(i));
2016-08-26 09:58:03 +00:00
goto again;
}
}
2019-08-10 08:57:14 +00:00
EX void generate(cell *wto) {
2016-08-26 09:58:03 +00:00
if(wto->wall == waBoat || wto->monst)
return;
2017-10-30 22:57:46 +00:00
wto->wall = waSea;
2016-08-26 09:58:03 +00:00
if(hrand(35000) < 40 + items[itWhirlpool] + yendor::hardness())
wto->monst = moCShark;
else if(hrand(5000) < 500)
wto->wall = waBoat;
2018-08-09 17:28:53 +00:00
if(wto->wall == waBoat && (eubinary || wto->master->alt)) {
2016-08-26 09:58:03 +00:00
int d = celldistAlt(wto);
if(yendor::on) d -= 200;
// 250 : hard
if(hrand(5000) < 60 + 3 * items[itWhirlpool] + yendor::hardness())
wto->monst = moPirate;
2017-08-14 18:31:43 +00:00
if(hrand(5000) < 20 && d < -20 && !tactic::on && !inv::on)
2016-08-26 09:58:03 +00:00
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);
2017-08-14 18:31:43 +00:00
else if(!inv::on && items[itWhirlpool] >= 10 && hrand(5000) < 20 && d < -15)
2016-08-26 09:58:03 +00:00
wto->item = itOrbWater;
else if(d<-10 && hrand(5000) < 1000-d)
wto->item = itWhirlpool;
}
}
void whirlMove(cell *wto, cell *wfrom) {
// monsters don't move
2017-03-23 10:53:57 +00:00
if(wfrom && (isPlayerOn(wfrom) || wfrom->monst))
2016-08-26 09:58:03 +00:00
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;
2017-03-23 10:53:57 +00:00
wto->mondir = neighborId(wto, wfrom);
2019-11-22 17:48:51 +00:00
animateMovement(moveimon(wto).rev(), LAYER_BOAT);
2016-08-26 09:58:03 +00:00
}
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; i<wto->type; i++) createMov(wto, i);
moveItem(wfrom, wto, false);
pickupMovedItems(wfrom);
pickupMovedItems(wto);
2016-08-26 09:58:03 +00:00
}
if(wto && !wfrom)
generate(wto);
}
2018-06-28 11:35:03 +00:00
void moveAt(cell *c, manual_celllister& cl) {
2016-08-26 09:58:03 +00:00
if(c->land != laWhirlpool) return;
if(cl.listed(c)) return;
2018-08-09 17:28:53 +00:00
if(!(eubinary || c->master->alt)) return;
2016-08-26 09:58:03 +00:00
cell *c2 = get(c, 1);
if(!c2) return;
int d = celldistAlt(c);
vector<cell*> whirlline;
whirlline.push_back(c);
whirlline.push_back(c2);
build(whirlline, d);
reverse(whirlline.begin(), whirlline.end());
build(whirlline, d);
2018-06-22 12:47:24 +00:00
int z = isize(whirlline);
2016-08-26 09:58:03 +00:00
for(int i=0; i<z; i++)
cl.add(whirlline[i]);
2016-08-26 09:58:03 +00:00
whirlMove(NULL, whirlline[0]);
for(int i=0; i<z-1; i++)
whirlMove(whirlline[i], whirlline[i+1]);
whirlMove(whirlline[z-1], NULL);
}
EX void move() {
2018-06-28 11:35:03 +00:00
manual_celllister cl;
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(dcal); i++) {
2016-08-26 09:58:03 +00:00
cell *c = dcal[i];
moveAt(c, cl);
2016-08-26 09:58:03 +00:00
}
// Keys and Orbs of Yendor always move
using namespace yendor;
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(yi); i++) {
moveAt(yi[i].path[0], cl);
moveAt(yi[i].path[YDIST-1], cl);
2016-08-26 09:58:03 +00:00
}
}
2019-08-10 08:57:14 +00:00
EX }
2016-08-26 09:58:03 +00:00
2019-08-10 08:57:14 +00:00
EX namespace mirror {
2016-08-26 09:58:03 +00:00
2019-08-10 08:57:14 +00:00
#if HDR
static const int SPINSINGLE = 1;
static const int SPINMULTI = 2;
static const int GO = 4;
static const int ATTACK = 8;
#endif
EX bool build(cell *c) {
if(kite::in() || sol) return false;
2019-02-17 17:28:20 +00:00
#if CAP_GP
if(GOLDBERG) {
2018-08-21 13:37:59 +00:00
if(c == c->master->c7) {
c->wall = ((gp::param.second == 0 || gp::param.first == gp::param.second) && hrand(2)) ? waMirror : waCloud;
return true;
}
return false;
}
2019-02-17 17:28:20 +00:00
#endif
#if CAP_ARCM
if(arcm::in()) {
2018-08-21 13:37:59 +00:00
c->wall = hrand(2) ? waMirror : waCloud;
return true;
}
2019-02-17 17:28:20 +00:00
#endif
if(bt::in() || IRREGULAR) {
2018-08-21 13:37:59 +00:00
// mirrors not supported
if(is_mirrorland(c)) {
c->item = itShard;
return true;
}
return false;
}
if(PURE?pseudohept(c):!ishept(c)) {
2018-08-21 13:37:59 +00:00
c->wall = hrand(2) ? waMirror : waCloud;
return true;
}
return false;
}
EX vector<pair<int, cellwalker>> mirrors;
#if HDR
constexpr int LIGHTNING = -1; // passed instead of cpid
#endif
2017-07-22 23:33:27 +00:00
bool noMirrorOn(cell *c) {
2019-11-30 14:03:53 +00:00
return c->monst || (!shmup::on && isPlayerOn(c)) || (!bounded && c->cpdist > gamerange());
2017-07-22 23:33:27 +00:00
}
2016-08-26 09:58:03 +00:00
bool cellMirrorable(cell *c) {
2017-07-22 23:33:27 +00:00
if(noMirrorOn(c)) return false;
2016-08-26 09:58:03 +00:00
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 ||
2017-07-16 21:00:55 +00:00
c->wall == waGargoyleBridge || c->wall == waTempFloor || c->wall == waTempBridge ||
c->wall == waMirrorWall || c->wall == waPetrifiedBridge;
2017-07-16 21:00:55 +00:00
}
2017-07-22 23:33:27 +00:00
void destroyKilled() {
int j = 0;
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(mirrors); i++)
if(mirrors[i].second.at->monst == moMimic)
2017-07-22 23:33:27 +00:00
mirrors[j++] = mirrors[i];
mirrors.resize(j);
}
2017-07-16 21:00:55 +00:00
2017-07-22 23:33:27 +00:00
void unlist() {
for(auto& m: mirrors)
if(m.second.at->monst == moMimic)
2020-03-02 12:17:30 +00:00
changes.ccell(m.second.at),
m.second.at->monst = moNone;
2016-08-26 09:58:03 +00:00
}
2017-07-22 23:33:27 +00:00
void list() {
for(auto& m: mirrors)
2020-03-02 12:17:30 +00:00
changes.ccell(m.second.at),
m.second.at->monst = moMimic;
2017-07-22 23:33:27 +00:00
}
EX void destroyAll() {
2020-03-02 12:17:30 +00:00
changes.value_keep(mirrors);
2017-07-22 23:33:27 +00:00
unlist();
mirrors.clear();
2016-08-26 09:58:03 +00:00
}
EX void createMirror(cellwalker cw, int cpid) {
2017-07-22 23:33:27 +00:00
if(!shmup::on && inmirror(cw))
cw = reflect(cw);
2020-03-02 12:17:30 +00:00
changes.ccell(cw.at);
2017-07-22 23:33:27 +00:00
if(cpid == LIGHTNING)
castLightningBolt(cw);
else if(cellMirrorable(cw.at)) {
2017-07-22 23:33:27 +00:00
for(auto& m: mirrors)
if(m == make_pair(cpid,cw))
return;
mirrors.emplace_back(cpid, cw);
}
2017-03-23 10:53:57 +00:00
}
2018-08-21 13:37:59 +00:00
2019-02-17 17:28:20 +00:00
#if CAP_ARCM
2018-08-21 13:37:59 +00:00
// we go by heptagons in Archimedean,
bool equal(heptspin h1, heptspin h2, int lev) {
if(h1.at->degree() != h2.at->degree()) return false;
if(arcm::is_vertex(h1.at) != arcm::is_vertex(h2.at)) return false;
2018-08-21 13:37:59 +00:00
if(lev) for(int i=0; i<h1.at->degree(); i++) {
heptspin h11 = h1 + i + wstep;
heptspin h21 = h2 + i + wstep;
if(!equal(h11, h21, lev-1)) return false;
}
return true;
}
int create_archimedean_rec(heptspin hs, int cpid, heptspin hs0, int lev) {
int result = 0;
for(int i=0; i<hs.at->degree(); i++) {
heptspin hs1 = hs + i;
if(lev == 0) {
if(hs1.at != hs0.at && equal(hs1, hs0, 3)) {
createMirror(hs1 + cth, cpid);
2018-08-21 13:37:59 +00:00
result++;
}
}
else result += create_archimedean_rec(hs1 + wstep, cpid, hs0, lev-1);
}
return result;
}
void create_archimedean(cellwalker cw, int cpid, bool mirrored) {
heptspin hs = cw + cth;
2018-08-21 13:37:59 +00:00
heptspin hsx = hs;
if(mirrored) hsx += wmirror;
if(create_archimedean_rec(hsx, cpid, hs, 2)) return;
if(create_archimedean_rec(hsx, cpid, hs, 3)) return;
if(create_archimedean_rec(hsx, cpid, hs, 4)) return;
}
2019-02-17 17:28:20 +00:00
#endif
2017-03-23 10:53:57 +00:00
EX void createMirrors(cellwalker cw, int cpid) {
2019-07-28 09:40:01 +00:00
if(kite::in() || sol) return;
2018-08-21 13:37:59 +00:00
2019-02-17 17:28:20 +00:00
#if CAP_ARCM
if(arcm::in()) {
2018-08-21 13:37:59 +00:00
create_archimedean(cw, cpid, true);
return;
}
2019-02-17 17:28:20 +00:00
#endif
2018-08-21 13:37:59 +00:00
2017-07-22 23:33:27 +00:00
cw.mirrored = !cw.mirrored;
cell *c = cw.at;
2016-08-26 09:58:03 +00:00
#if MAXMDIM >= 4
if(WDIM == 3 && !bt::in()) {
if(shmup::on) for(int i=0; i<cw.at->type; i++)
createMirror(cw + i + wstep - i, cpid);
return;
}
#endif
2019-02-17 17:28:20 +00:00
#if CAP_GP
if(GOLDBERG) {
2018-08-21 13:37:59 +00:00
for(int i=0; i<cw.at->type; i++) {
createMirror(cw + cth + i + wstep + i - (gp::param.first == gp::param.second ? 1 : 0) + cth, cpid);
2018-08-21 13:37:59 +00:00
}
return;
}
2019-02-17 17:28:20 +00:00
#endif
for(int i=0; i<cw.at->type; i++) {
2018-03-24 11:59:01 +00:00
auto cws = cw + wstep;
if(cws.at->type == c->type)
2018-03-24 11:59:01 +00:00
createMirror(cws+i, cpid);
cw += 1;
2016-08-26 09:58:03 +00:00
}
}
EX void createMirages(cellwalker cw, int cpid) {
2019-02-17 17:28:20 +00:00
#if CAP_ARCM
if(arcm::in()) {
2018-08-21 13:37:59 +00:00
create_archimedean(cw, cpid, false);
return;
}
2019-02-17 17:28:20 +00:00
#endif
#if MAXMDIM >= 4
if(WDIM == 3 && !bt::in()) {
if(shmup::on) for(int i=0; i<cw.at->type; i++)
createMirror(cw + i + wstep - i, cpid);
return;
}
#endif
2019-02-17 17:28:20 +00:00
#if CAP_GP
if(GOLDBERG && !(S7 & 1)) {
2018-08-21 13:37:59 +00:00
for(int i=0; i<cw.at->type; i++) {
createMirror(cw + cth + i + wstep + 1 + wstep + 1 + (S7/2) - i + 1 + cth, cpid);
2018-08-21 13:37:59 +00:00
}
return;
}
if(GOLDBERG && (S7 & 1)) {
2018-08-21 13:37:59 +00:00
for(int i=0; i<cw.at->type; i++) {
createMirror(cw + cth + i + wstep + (S7/2) + wstep - 2 + wstep + (S7/2) - i + cth, cpid);
2018-08-21 13:37:59 +00:00
}
return;
}
2019-02-17 17:28:20 +00:00
#endif
if(PURE && !(S7 & 1)) {
2018-08-21 13:37:59 +00:00
for(int i=0; i<cw.at->type; i++)
createMirror(cw + i + wstep + 1 + wstep + 1 + (S7/2) - i, cpid);
return;
}
if(PURE && (S7 & 1) && (S3 == 4)) {
2018-08-21 13:37:59 +00:00
for(int i=0; i<cw.at->type; i++)
createMirror(cw + i + wstep + 1 + wstep - (S7/2) + wstep - (S7/2) - i, cpid);
return;
}
if(PURE && (S7 & 1)) {
for(int i=0; i<cw.at->type; i++)
2018-08-21 13:37:59 +00:00
createMirror(cw + i + wstep + (S7/2) + wstep - 2 + wstep + (S7/2) - i, cpid);
2016-08-26 09:58:03 +00:00
return;
}
for(int i=0; i<S6; i++) {
2018-03-24 11:59:01 +00:00
auto cw0 = cw + i + wstep;
if(!ctof(cw0.at)) {
2018-03-24 11:59:01 +00:00
createMirror(cw0 + 2 + wstep - (2+i), cpid);
createMirror(cw0 - 2 + wstep + (2-i), cpid);
2017-07-22 23:33:27 +00:00
}
2016-08-26 09:58:03 +00:00
}
}
2017-07-22 23:33:27 +00:00
EX void createHere(cellwalker cw, int cpid) {
if(!cw.at) return;
if(cw.at->wall == waCloud)
2017-07-22 23:33:27 +00:00
createMirages(cw, cpid);
if(cw.at->wall == waMirror)
2017-07-22 23:33:27 +00:00
createMirrors(cw, cpid);
2016-08-26 09:58:03 +00:00
}
EX void breakMirror(cellwalker cw, int pid) {
if(!cw.at) return;
cell *c = cw.at;
2017-07-22 23:33:27 +00:00
if(c->wall == waMirror || c->wall == waCloud) {
drawParticles(c, winf[c->wall].color, 16);
playSound(c, "pickup-mirror", 50);
if(pid >= 0 && (cw.at->land == laMirror || cw.at->land == laMirrorOld)) {
2017-07-22 23:33:27 +00:00
dynamicval<int> d(multi::cpid, pid);
gainShard(cw.at, c->wall == waMirror ? "The mirror shatters!" : "The cloud turns into a bunch of images!");
2016-08-26 09:58:03 +00:00
}
2020-03-02 12:17:30 +00:00
changes.ccell(c);
2017-07-22 23:33:27 +00:00
c->wall = waNone;
2016-08-26 09:58:03 +00:00
}
}
2017-07-22 23:33:27 +00:00
EX bool isKilledByMirror(cell *c) {
for(auto& m: mirrors) {
cell *c1 = (m.second + wstep).at;
if(inmirror(c1)) c1 = reflect(cellwalker(c1, 0, false)).at;
if(c1 == c && canAttack(m.second.at, moMimic, c, c->monst, 0))
2017-07-22 23:33:27 +00:00
return true;
}
2017-07-22 23:33:27 +00:00
return false;
}
2016-08-26 09:58:03 +00:00
void go(bool fwd) {
2020-03-02 12:17:30 +00:00
changes.value_keep(mirrors);
2016-08-26 09:58:03 +00:00
int tk = tkills();
2017-07-22 23:33:27 +00:00
int nummirage = 0;
int j = 0;
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(mirrors); i++) {
2017-07-22 23:33:27 +00:00
auto& m = mirrors[i];
bool survive = true;
if(m.first == multi::cpid) {
cell *c = m.second.at;
2020-02-29 16:58:59 +00:00
changes.ccell(c);
2017-07-22 23:33:27 +00:00
if(!m.second.mirrored) nummirage++;
2018-03-24 11:59:01 +00:00
auto cw2 = m.second + wstep;
2017-07-22 23:33:27 +00:00
if(inmirror(cw2)) cw2 = reflect(cw2);
cell *c2 = cw2.at;
2020-02-29 16:58:59 +00:00
changes.ccell(c2);
2017-07-22 23:33:27 +00:00
if(c2->monst) {
c->monst = moMimic;
2017-09-30 09:46:41 +00:00
eMonster m2 = c2->monst;
if(!peace::on && canAttack(c,moMimic,c2,m2, 0)) {
attackMonster(c2, AF_NORMAL | AF_MSG, moMimic);
2017-09-30 09:46:41 +00:00
if(!fwd) produceGhost(c2, m2, moMimic);
sideAttack(c, m.second.spin, m2, 0);
}
2017-07-22 23:33:27 +00:00
c->monst = moNone;
2017-07-16 21:00:55 +00:00
}
2016-08-26 09:58:03 +00:00
if(c2->wall == waBigTree)
c2->wall = waSmallTree;
else if(c2->wall == waSmallTree)
c2->wall = waNone;
2017-07-22 23:33:27 +00:00
if(fwd) {
if(noMirrorOn(c2) || !passable_for(moMimic, c2, c, P_MONSTER | P_MIRROR | P_MIRRORWALL)) {
2017-07-22 23:33:27 +00:00
survive = false;
continue;
}
c->monst = moMimic;
moveMonster(m.second);
2016-08-26 09:58:03 +00:00
c2->monst = moNone;
empathyMove(m.second);
2017-07-22 23:33:27 +00:00
m.second = cw2;
2016-08-26 09:58:03 +00:00
}
}
2017-07-22 23:33:27 +00:00
if(survive) {
mirrors[j++] = m;
2016-08-26 09:58:03 +00:00
}
}
2017-07-22 23:33:27 +00:00
mirrors.resize(j);
2016-08-26 09:58:03 +00:00
achievement_count("MIRRORKILL", tkills(), tk);
achievement_count("MIRAGE", nummirage, 0);
}
2019-08-10 08:57:14 +00:00
EX void act(int d, int flags) {
2020-02-29 16:58:59 +00:00
changes.value_keep(mirrors);
2017-07-22 23:33:27 +00:00
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)
2018-03-24 11:59:01 +00:00
m.second += d;
2017-07-22 23:33:27 +00:00
}
if(flags & ATTACK)
go(flags & GO);
list();
}
EX void breakAll() {
2020-02-29 16:58:59 +00:00
changes.value_keep(mirrors);
2017-07-22 23:33:27 +00:00
destroyKilled();
unlist();
if(numplayers() == 1)
createHere(cwt, 0);
else for(int i=0; i<numplayers(); i++)
createHere(multi::player[i], i);
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(mirrors); i++)
2017-07-22 23:33:27 +00:00
createHere(mirrors[i].second, mirrors[i].first);
if(numplayers() == 1)
breakMirror(cwt, 0);
else for(int i=0; i<numplayers(); i++)
breakMirror(multi::player[i], i);
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(mirrors); i++)
2017-07-22 23:33:27 +00:00
breakMirror(mirrors[i].second, -1);
list();
2017-03-23 10:53:57 +00:00
}
2017-07-16 21:00:55 +00:00
EX int mirrordir(cell *c) {
2017-07-16 21:00:55 +00:00
if(c->type == 7) return c->bardir;
int icount = 0, isum = 0;
for(int i=0; i<6; i+=2) {
if(createMov(c, i)->bardir == c->c.spin(i))
2017-07-16 21:00:55 +00:00
icount++, isum+=i;
}
2017-07-22 23:33:27 +00:00
if(icount != 1) return -1;
2017-07-16 21:00:55 +00:00
return isum;
}
pair<bool, cellwalker> traceback(vector<int>& v, cellwalker cw) {
bool goout = false;
2018-06-22 12:47:24 +00:00
for(int i=isize(v)-1; i>=0; i--) {
2018-03-24 11:59:01 +00:00
if(v[i]) cw -= v[i];
2017-07-16 21:00:55 +00:00
else {
2018-03-24 11:59:01 +00:00
cw += wstep;
if(cw.at->land == laMirrorWall || cw.at->land == laMirror) goout = true;
2017-07-16 21:00:55 +00:00
}
}
return make_pair(goout, cw);
}
2017-08-06 12:50:16 +00:00
int depth(cell *c) { return c->landparam & 255; }
2017-03-23 10:53:57 +00:00
2017-09-17 10:32:31 +00:00
cellwalker reflect0(cellwalker cw) {
2017-07-16 21:00:55 +00:00
int stepcount = 0;
cellwalker cwcopy = cw;
static vector<int> v;
v.clear();
while(true) {
if(!inmirror(cw)) break;
stepcount++; if(stepcount > 10000) {
2017-07-22 23:33:27 +00:00
return cw;
2017-07-16 21:00:55 +00:00
}
cell *c0 = (cw+wstep).at;
2017-07-16 21:00:55 +00:00
int go = 0;
if(!inmirror(c0)) go = 2;
else if(depth(c0) && depth(c0) < depth(cw.at)) go = 1;
2017-07-16 21:00:55 +00:00
if(go) {
v.push_back(0);
2018-03-24 11:59:01 +00:00
cw += wstep;
2017-07-16 21:00:55 +00:00
if(go == 2) break;
}
else {
v.push_back(1);
2018-03-24 11:59:01 +00:00
cw += 1;
2017-07-16 21:00:55 +00:00
}
}
if(cw.at->land == laMirrorWall || cw.at->land == laMirrorWall2) {
if(cw.at->type == 7) {
while(cw.spin != cw.at->bardir) {
2018-03-24 11:59:01 +00:00
cw += 1;
2017-07-16 21:00:55 +00:00
v.push_back(1);
stepcount++; if(stepcount > 10000) { printf("failhep\n"); return cw; }
}
if(PURE && (cw+wstep).at == cwcopy.at)
2017-07-16 21:00:55 +00:00
v.pop_back();
if(PURE && (cw+3+wstep).at->land == laMirrored && (cw+2+wstep).at->land == laMirrorWall) {
2018-03-24 11:59:01 +00:00
cw += wmirror;
2017-07-16 21:00:55 +00:00
auto p = traceback(v, cw);
if(p.first) return p.second;
2018-03-24 11:59:01 +00:00
cw += 2;
2017-07-16 21:00:55 +00:00
v.push_back(2);
2018-03-24 11:59:01 +00:00
cw += wstep;
2017-07-16 21:00:55 +00:00
v.push_back(0);
2018-03-24 11:59:01 +00:00
cw += 3;
2017-07-16 21:00:55 +00:00
v.push_back(3);
}
}
else {
while((cw+wstep).at->type != 7) {
2018-03-24 11:59:01 +00:00
cw ++;
2017-07-16 21:00:55 +00:00
v.push_back(1);
}
int icount = 0;
for(int i=0; i<3; i++) {
if((cw+wstep).at->bardir == cw.at->c.spin(cw.spin))
2017-07-16 21:00:55 +00:00
icount++;
2018-03-24 11:59:01 +00:00
cw += 2;
2017-07-16 21:00:55 +00:00
}
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;
2018-03-24 11:59:01 +00:00
cw += (a*2);
2017-07-16 21:00:55 +00:00
auto p = traceback(v,cw);
if(p.first) return p.second;
}
printf("icount >= 2 but failed\n");
return cw;
}
while((cw+wstep).at->bardir != cw.at->c.spin(cw.spin)) {
2017-07-16 21:00:55 +00:00
stepcount++; if(stepcount > 10000) { printf("fail2\n"); return cw; }
2018-03-24 11:59:01 +00:00
cw += 2;
2017-07-16 21:00:55 +00:00
v.push_back(1);
v.push_back(1);
}
}
}
else v.pop_back();
cw.mirrored = !cw.mirrored;
cw = traceback(v,cw).second;
return cw;
}
2017-07-22 23:33:27 +00:00
2017-08-06 12:50:16 +00:00
static const int CACHESIZE = 1<<12; // must be a power of 2
static const int CACHEMASK = CACHESIZE-1;
pair<cell*, cellwalker> cache[CACHESIZE];
int nextcache;
void clearcache() {
for(int i=0; i<CACHESIZE; i++) cache[i].first = NULL;
}
EX cellwalker reflect(const cellwalker& cw) {
if(!cw.at) return cw;
if((cw.at->landparam & 255) == 0) {
bool cando = false;
forCellEx(c2, cw.at) if(c2->landparam & 255) cando = true;
if(cando) buildEquidistant(cw.at);
}
if((cw.at->landparam & 255) == 0) return cw;
int cid = (cw.at->landparam >> 8) & CACHEMASK;
if(cache[cid].first != cw.at) {
2017-08-06 12:50:16 +00:00
cid = nextcache++;
nextcache &= CACHEMASK;
cw.at->landparam &= ~ (CACHEMASK << 8);
cw.at->landparam |= (cid << 8);
cache[cid].first = cw.at;
cellwalker cw0(cw.at, 0, false);
2017-09-17 10:32:31 +00:00
cache[cid].second = reflect0(cw0);
int tries = 64;
while(inmirror(cache[cid].second.at) && tries--)
2017-09-17 10:32:31 +00:00
cache[cid].second = reflect0(cache[cid].second);
2017-08-06 12:50:16 +00:00
}
2018-03-24 11:59:01 +00:00
cellwalker res = cache[cid].second + cw.spin;
2017-08-06 12:50:16 +00:00
if(cw.mirrored) res.mirrored = !res.mirrored;
return res;
2017-07-16 21:00:55 +00:00
}
2017-08-06 12:50:16 +00:00
2019-08-10 08:57:14 +00:00
EX }
2016-08-26 09:58:03 +00:00
2019-08-10 08:57:14 +00:00
EX namespace hive {
2016-08-26 09:58:03 +00:00
EX int hivehard() {
2017-10-08 10:10:40 +00:00
return items[itRoyalJelly] + hardness_empty();
// 0, 5, 40, 135
}
EX eMonster randomHyperbug() {
2017-10-08 10:10:40 +00:00
int h = hivehard();
if(hrand(200) < h)
return moBug2;
return eMonster(moBug0 + hrand(BUGCOLORS));
// 50: 25/25/50
// 100:
}
2016-08-26 09:58:03 +00:00
struct buginfo_t {
cell *where;
short dist[BUGCOLORS];
};
vector<buginfo_t> buginfo;
vector<int> bugqueue[BUGCOLORS];
vector<int> 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_t> bugtomove;
vector<cell*> deadbug;
vector<cell*> bugcellq;
EX int bugcount[BUGCOLORS];
2016-08-26 09:58:03 +00:00
bool isBugEnemy(cell *c, int k) {
2017-03-23 10:53:57 +00:00
if(isPlayerOn(c) && !invismove) return true;
2016-08-26 09:58:03 +00:00
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) {
int& i(c->listindex);
2018-06-22 12:47:24 +00:00
if(i >= 0 && i < isize(buginfo) && buginfo[i].where == c)
2016-08-26 09:58:03 +00:00
return;
2018-06-22 12:47:24 +00:00
i = isize(buginfo);
2016-08-26 09:58:03 +00:00
buginfo.resize(i+1);
buginfo_t& b(buginfo[i]);
b.where = c;
for(int k=0; k<BUGCOLORS; k++) {
b.dist[k] = BUGINF;
bool havebug = false, haveother = false;
for(int dir=0; dir<c->type; dir++) {
cell *c2 = c->move(dir);
2016-08-26 09:58:03 +00:00
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
2018-06-22 12:47:24 +00:00
if(isize(buginfo) < 30000) {
2016-08-26 09:58:03 +00:00
for(int dir=0; dir<c->type; dir++) {
cell *c2 = c->move(dir);
2016-08-26 09:58:03 +00:00
if(c2) {
// if(isBug(c)) bugcellq.push_back(c2); => does not help...
for(int t=0; t<c2->type; t++)
if(c2->move(t) && isBug(c2->move(t)))
2016-08-26 09:58:03 +00:00
bugcellq.push_back(c2),
bugcellq.push_back(c2->move(t));
2016-08-26 09:58:03 +00:00
}
}
}*/
// use pheromones!
if(c->land == laHive && c->landparam > 1 && c->wall != waWaxWall) {
c->landparam --;
for(int dir=0; dir<c->type; dir++) {
cell *c2 = c->move(dir);
2016-08-26 09:58:03 +00:00
if(c2) {
for(int t=0; t<c2->type; t++)
if(c2->move(t))
2016-08-26 09:58:03 +00:00
bugcellq.push_back(c2),
bugcellq.push_back(c2->move(t));
2016-08-26 09:58:03 +00:00
}
}
}
}
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; dir<c->type; dir++) {
cell *c2 = c->move(dir);
2016-08-26 09:58:03 +00:00
if(!c2) continue;
if(c2->listindex < 0 || c2->listindex >= isize(buginfo)) continue;
2016-08-26 09:58:03 +00:00
if(!passable(c, c2, P_MONSTER)) continue;
int j = c2->listindex;
2016-08-26 09:58:03 +00:00
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));
}
}
bool fightspam(cell *c) {
return c->cpdist >= gamerange() ||
2016-08-26 09:58:03 +00:00
isMetalBeast(c->monst) || c->monst == moSkeleton ||
isIvy(c->monst) || isMutantIvy(c->monst);
}
EX void movebugs() {
2016-08-26 09:58:03 +00:00
buginfo.clear();
for(int k=0; k<BUGCOLORS; k++) bugqueue[k].clear();
for(int k=0; k<BUGCOLORS; k++) bugqueue4[k].clear();
for(int k=0; k<BUGCOLORS; k++) bugcount[k] = 0;
bugtomove.clear();
deadbug.clear();
2018-06-22 12:47:24 +00:00
int xdcs = isize(dcal); for(int i=0; i<xdcs; i++) bugcell(dcal[i]);
for(int i=0; i<isize(bugcellq); i++) bugcell(bugcellq[i]);
2016-08-26 09:58:03 +00:00
bugcellq.clear();
2018-06-22 12:47:24 +00:00
// printf("buginfo = %d\n", isize(buginfo));
2016-08-26 09:58:03 +00:00
for(int k=0; k<BUGCOLORS; k++) {
int t = 0;
last_d = -1;
int invadist = 4 - (items[itRoyalJelly]+10) / 20;
if(invadist<0) invadist = 0;
2018-06-22 12:47:24 +00:00
for(; t<isize(bugqueue[k]) && last_d < invadist-1; t++) handleBugQueue(k, t);
for(int u=0; u<isize(bugqueue4[k]); u++)
2016-08-26 09:58:03 +00:00
bugQueueInsert(k, bugqueue4[k][u], invadist);
bugqueue4[k].clear();
2018-06-22 12:47:24 +00:00
for(; t<isize(bugqueue[k]); t++) handleBugQueue(k, t);
2016-08-26 09:58:03 +00:00
}
for(int k=0; k<BUGCOLORS; k++) {
set<int> check;
2018-06-22 12:47:24 +00:00
for(int t=0; t<isize(bugqueue[k]); t++) {
2016-08-26 09:58:03 +00:00
if(check.count(bugqueue[k][t])) {
printf("REPETITION! [%d]\n", t);
}
check.insert(bugqueue[k][t]);
}
}
2018-06-22 12:47:24 +00:00
hrandom_shuffle(&bugtomove[0], isize(bugtomove));
2016-08-26 09:58:03 +00:00
sort(bugtomove.begin(), bugtomove.end());
int battlecount = 0;
2018-06-22 12:47:24 +00:00
for(int t=0; t<isize(bugtomove); t++) {
2016-08-26 09:58:03 +00:00
bugtomove_t& bm(bugtomove[t]);
int i = bm.index;
buginfo_t& b(buginfo[i]);
cell *c = b.where;
if(!isBug(c)) continue;
if(c->stuntime) 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; dir<c->type; dir++) {
cell *c2 = c->move(dir);
2016-08-26 09:58:03 +00:00
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->listindex < 0 || c2->listindex >= isize(buginfo)) qual = -15;
else if(buginfo[c2->listindex].where != c2) qual = -15;
else if(buginfo[c2->listindex].dist[k] < b.dist[k])
2016-08-26 09:58:03 +00:00
qual = 1;
else if(buginfo[c2->listindex].dist[k] == b.dist[k])
2016-08-26 09:58:03 +00:00
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)];
2019-11-22 17:19:22 +00:00
movei mi(c, d);
auto& c2 = mi.t;
2017-06-09 01:41:33 +00:00
if(c2->monst || isPlayerOn(c2)) {
2016-08-26 09:58:03 +00:00
eMonster killed = c2->monst;
2017-06-09 01:41:33 +00:00
if(isPlayerOn(c2)) killed = moPlayer;
2016-08-26 09:58:03 +00:00
if(isBug(killed)) battlecount++;
2017-06-09 01:41:33 +00:00
else if(killed != moPlayer && !fightspam(c2))
addMessage(XLAT("%The1 fights with %the2!", c->monst, killed));
attackMonster(c2, AF_NORMAL | AF_GETPLAYER, c->monst);
2016-08-26 09:58:03 +00:00
// killMonster(c);
if(isBug(killed)) {
c2->monst = moDeadBug, deadbug.push_back(c2);
bugcount[killed - moBug0]--;
}
// c->monst = moDeadBug, deadbug.push_back(c);
}
else {
2019-11-22 17:19:22 +00:00
moveMonster(mi);
2016-08-26 09:58:03 +00:00
// pheromones!
if(c->land == laHive && c->landparam < 90) c->landparam += 5;
if(c2->land == laHive && c2->landparam < 90) c2->landparam += 5;
/* if(c2->item == itRoyalJelly && !isQueen(m)) {
// advance!
c2->monst = eMonster(m+BUGCOLORS);
c2->item = itNone;
} */
}
}
// cleanup
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(deadbug); i++) deadbug[i]->monst = moNone;
2016-08-26 09:58:03 +00:00
if(battlecount)
addMessage(XLAT("The Hyperbugs are fighting!"));
int maxbug = 0;
for(int k=0; k<BUGCOLORS; k++) if(bugcount[k] > maxbug) maxbug = bugcount[k];
achievement_count("BUG", maxbug, 0);
}
void bugcitycell(cell *c, int d) {
int& i = c->listindex;
2018-06-22 12:47:24 +00:00
if(i >= 0 && i < isize(buginfo) && buginfo[i].where == c)
2016-08-26 09:58:03 +00:00
return;
2018-06-22 12:47:24 +00:00
i = isize(buginfo);
2016-08-26 09:58:03 +00:00
buginfo_t b;
b.where = c;
b.dist[0] = d;
buginfo.push_back(b);
}
2019-08-10 08:57:14 +00:00
EX void createBugArmy(cell *c) {
2016-08-26 09:58:03 +00:00
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; i<c->type; 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; i<c->type; i++) {
if(c->move(i) && c->move(i)->mpdist < c->mpdist) gdir = i;
2016-08-26 09:58:03 +00:00
}
if(!gdir) return;
cellwalker bf(c, gdir);
int radius = 9;
2017-11-03 21:25:08 +00:00
if(getDistLimit() <= 6) radius = 6;
2016-08-26 09:58:03 +00:00
if(chaosmode) radius = 5;
2017-11-03 21:25:08 +00:00
if(chaosmode && getDistLimit() <= 5) radius = 4;
if(getDistLimit() <= 3) radius = 3;
2018-12-12 01:51:42 +00:00
for(int i=(chaosmode?0:2); i<radius; i++)
bf += revstep;
cell *citycenter = bf.at;
2016-08-26 09:58:03 +00:00
buginfo.clear();
// mark the area with BFS
bugcitycell(citycenter, 0);
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(buginfo); i++) {
2016-08-26 09:58:03 +00:00
buginfo_t& b(buginfo[i]);
cell *c = b.where;
int d = b.dist[0];
// ERRORS!
if(c->land != 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; t<c->type; t++)
bugcitycell(createMov(c,t), d+1);
}
// place everything
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(buginfo); i++) {
2016-08-26 09:58:03 +00:00
buginfo_t& b(buginfo[i]);
cell *c = b.where;
int d = b.dist[0];
if(d <= 1 && c->wall == waNone)
c->item = itRoyalJelly;
2017-03-23 10:53:57 +00:00
preventbarriers(c);
2016-08-26 09:58:03 +00:00
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;
}
}
2019-08-10 08:57:14 +00:00
EX }
2016-08-26 09:58:03 +00:00
#if HDR
2016-08-26 09:58:03 +00:00
inline float& HEAT(cell *c) { return c->LHU.heat; }
#endif
2016-08-26 09:58:03 +00:00
EX namespace heat {
2016-08-26 09:58:03 +00:00
EX void affect(cell *c, double delta) {
2017-09-30 09:46:41 +00:00
if(isIcyLand(c)) HEAT(c) += delta;
}
EX double absheat(cell *c) {
2016-08-26 09:58:03 +00:00
if(c->land == laCocytus) return HEAT(c) -.6;
2020-02-26 00:37:17 +00:00
if(c->land == laIce || c->land == laBlizzard || c->land == laEclectic) return HEAT(c) -.4;
2016-08-26 09:58:03 +00:00
return 0;
}
EX double celsius(cell *c) { return absheat(c) * 60; }
2016-08-26 09:58:03 +00:00
2017-03-23 10:53:57 +00:00
// adjust to the improved heat transfer algorithm in 9.4
const float FIX94 = 1.5;
EX vector<cell*> offscreen_heat, offscreen_fire; // offscreen cells to take care off
EX void processheat(double rate IS(1)) {
2016-08-26 09:58:03 +00:00
if(markOrb(itOrbSpeed)) rate /= 2;
2018-11-25 22:37:14 +00:00
if(racing::on) return;
int oldmelt = kills[0];
2016-08-26 09:58:03 +00:00
vector<cell*> offscreen2;
2018-06-28 11:35:03 +00:00
manual_celllister cl;
int gr = gamerange();
for(cell *c: offscreen_heat) {
if(c->cpdist > gr && !doall) {
if(!cl.add(c)) continue;
2016-08-26 09:58:03 +00:00
if(isIcyLand(c)) {
if(HEAT(c) < .01 && HEAT(c) > -.01)
HEAT(c) = 0;
else {
HEAT(c) *= 1 - rate/10;
offscreen2.push_back(c);
2016-08-26 09:58:03 +00:00
}
}
}
}
offscreen_heat = move(offscreen2);
2016-08-26 09:58:03 +00:00
for(int i=0; i<numplayers(); i++) {
cell *c = playerpos(i);
2017-03-23 10:53:57 +00:00
if(!c) continue;
2016-08-26 09:58:03 +00:00
double xrate = (c->land == laCocytus && shmup::on) ? rate/3 : rate;
if(PURE) xrate *= 1.7; // todo-variation
2017-03-23 10:53:57 +00:00
if(!shmup::on) xrate /= FIX94;
2016-08-26 09:58:03 +00:00
if(isIcyLand(c))
HEAT(c) += (markOrb(itOrbWinter) ? -1.2 : 1.2) * xrate;
}
vector<cell*>& allcells = currentmap->allcells();
2017-03-23 10:53:57 +00:00
2018-06-22 12:47:24 +00:00
int dcs = isize(allcells);
2017-03-23 10:53:57 +00:00
vector<ld> hmods(dcs, 0);
2016-08-26 09:58:03 +00:00
int divby = 10;
if(S7 > 10) divby *= 2;
if(arcm::in()) divby *= 2;
2019-05-08 16:33:08 +00:00
if(WDIM == 3) divby *= 2;
2016-08-26 09:58:03 +00:00
for(int i=0; i<dcs; i++) {
2017-03-23 10:53:57 +00:00
cell *c = allcells[i];
double xrate = (c->land == laCocytus && shmup::on) ? 1/3. : 1;
if(PURE) xrate *= 1.7; // todo-variation
2017-03-23 10:53:57 +00:00
if(!shmup::on) xrate /= FIX94;
if(c->cpdist > gr && !doall) break;
2016-08-26 09:58:03 +00:00
if(isIcyLand(c)) {
2017-03-23 10:53:57 +00:00
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;
2016-08-26 09:58:03 +00:00
if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM)
2017-03-23 10:53:57 +00:00
hmod += (c->land == laCocytus ? 1.5 : 10) * xrate;
2016-08-26 09:58:03 +00:00
if(c->monst == moGreaterShark)
2017-03-23 10:53:57 +00:00
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;
2016-08-26 09:58:03 +00:00
auto ls = adj_minefield_cells(c);
for(cell* ct: ls) {
2017-10-10 10:42:30 +00:00
if(!isIcyLand(ct) && isFire(ct))
hmod += xrate*.1;
if(ct->land == laVolcano)
hmod += xrate * (ct->wall == waMagma ? .4 : .2);
}
2016-08-26 09:58:03 +00:00
for(cell* ct: ls) {
2017-09-30 09:46:41 +00:00
if(!isIcyLand(ct)) {
2016-08-26 09:58:03 +00:00
// make sure that we can still enter Cocytus,
// it won't heat up right away even without Orb of Winter or Orb of Speed
2017-10-16 23:51:27 +00:00
if(isPlayerOn(ct) && (c->land != laCocytus || markOrb(itOrbWinter)))
hmod += (markOrb(itOrbWinter) ? -1.2 : 1.2) / 4 * xrate;
2016-08-26 09:58:03 +00:00
continue;
}
2017-09-30 09:46:41 +00:00
ld hdiff = absheat(ct) - absheat(c);
hdiff /= divby;
2017-09-30 09:46:41 +00:00
2019-02-17 17:33:15 +00:00
#if CAP_FIELD
if(ct->land == laBlizzard && c->land == laBlizzard) {
2017-09-30 09:46:41 +00:00
int v = (windmap::at(ct) - windmap::at(c)) & 255;
if(v > 128) v -= 256;
if(v < windmap::NOWINDFROM && v > -windmap::NOWINDFROM)
hdiff = hdiff * (1 - v * 5. / windmap::NOWINDFROM);
}
2019-02-17 17:33:15 +00:00
#endif
2017-09-30 09:46:41 +00:00
if(shmup::on && (c->land == laCocytus || ct->land == laCocytus))
2016-08-26 09:58:03 +00:00
hdiff /= 3;
// if(c->move(j)->cpdist > 7 && !quotient) hdiff += -HEAT(c) / 30;
2016-08-26 09:58:03 +00:00
hmod += hdiff;
}
2017-09-30 09:46:41 +00:00
// printf("%d ", vsum);
2016-08-26 09:58:03 +00:00
hmods[i] = hmod;
2016-08-26 09:58:03 +00:00
}
if(HEAT(c) && !doall)
offscreen_heat.push_back(c);
2016-08-26 09:58:03 +00:00
}
#if HDR
2017-03-23 10:53:57 +00:00
#define MELTCOLOR 0xA04040
#endif
2017-03-23 10:53:57 +00:00
2016-08-26 09:58:03 +00:00
for(int i=0; i<dcs; i++) {
2017-03-23 10:53:57 +00:00
cell *c = allcells[i];
2017-07-22 23:33:27 +00:00
if(!isIcyLand(c)) continue;
2017-03-23 10:53:57 +00:00
HEAT(c) += hmods[i] * rate;
if(c->monst == 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]++;
2016-08-26 09:58:03 +00:00
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));
2017-03-23 10:53:57 +00:00
fallMonster(c);
2016-08-26 09:58:03 +00:00
}
}
}
if(kills[0] != oldmelt) bfs();
}
vector<pair<cell*, int> > newfires;
EX void processfires() {
newfires.clear();
vector<cell*> offscreen2;
2018-06-28 11:35:03 +00:00
manual_celllister cl;
vector<cell*>& allcells = currentmap->allcells();
2017-06-09 01:41:33 +00:00
for(int x: {0,1}) for(cell *c: x==0 ? allcells : offscreen_fire) {
if(!cl.add(c)) continue;
2017-12-21 14:12:25 +00:00
if(isFireOrMagma(c)) {
if(c->wall == waMagma) c->wparam = 20;
2016-08-26 09:58:03 +00:00
cell *last = c->move(c->type-1);
auto ls = adj_minefield_cells(c);
for(cell* c2: ls) {
if(c->wall == waPartialFire) {
// two partial fires adjacent are necessary to spread
bool ok = false;
forCellEx(c3, c2) if(c3 != c && c3->wall == waPartialFire)
ok = true;
if(!ok) continue;
}
if(c2->wall == waNone && c2->land == laRose && c->wparam >= 10)
newfires.emplace_back(c2, c->wparam);
if(c2->wall == waFire && c2->land == laRose && c->wparam >= 10 && c2->wparam < c->wparam/2)
newfires.emplace_back(c2, c->wparam);
if(againstWind(c, c2) && c->wall != waEternalFire && c->wparam >= 10) {
if(isFire(c2)) {
if(c2->wparam < c->wparam/2)
newfires.emplace_back(c2, c->wparam);
}
else {
newfires.emplace_back(c2, c->wparam);
useup(c);
}
}
if(c2->land == laDryForest && c2->wall != waEternalFire) {
c2->landparam++;
if(c2->landparam >= (isStandardTree(c2) ? 1 : 10)) newfires.emplace_back(c2, 12);
else offscreen2.push_back(c);
}
else if(c2->wall == waVinePlant || c2->wall == waRose || c2->wall == waSaloon ||
c2->wall == waWeakBranch || c2->wall == waCanopy || c2->wall == waTrunk || c2->wall == waSolidBranch ||
2017-12-29 00:10:47 +00:00
c2->wall == waBigBush || c2->wall == waSmallBush || c2->wall == waBonfireOff || c2->wall == waSmallTree ||
c2->wall == waDock)
newfires.emplace_back(c2, 12);
else if(cellHalfvine(c2) && last && last->wall == c2->wall)
newfires.emplace_back(c2, 12);
2018-12-16 23:04:59 +00:00
else if(c2->wall == waExplosiveBarrel)
newfires.emplace_back(c2, 12);
else if(c2->wall == waFireTrap)
newfires.emplace_back(c2, 12);
// both halfvines have to be near fire at once
last = c2;
2017-06-09 01:41:33 +00:00
}
2016-08-26 09:58:03 +00:00
}
2017-06-09 01:41:33 +00:00
if(hasTimeout(c)) {
2017-12-29 00:10:47 +00:00
if(c->mpdist == 8 && (c->land == laWineyard || c->land == laEndorian || c->land == laDocks)) {
// do not expire, do not store in 'offscreen', do not generate more land
}
else {
useup(c);
offscreen2.push_back(c);
}
2017-09-30 09:46:41 +00:00
}
2017-10-12 09:33:26 +00:00
if(c->wall == waArrowTrap && c->wparam && !shmup::on) {
2017-09-30 09:46:41 +00:00
c->wparam++;
if(c->wparam == 3) {
if(canAttack(c, moArrowTrap, c, c->monst, AF_GETPLAYER))
attackMonster(c, AF_NORMAL | AF_MSG | AF_GETPLAYER, moArrowTrap);
2017-09-30 09:46:41 +00:00
}
if(c->wparam == 4) c->wparam = 0;
}
2018-12-16 23:04:59 +00:00
if(c->wall == waFireTrap && c->wparam && !shmup::on) {
c->wparam++;
if(c->wparam == 3) {
c->wall = waNone;
explosion(c, 5, 10);
}
}
2016-08-26 09:58:03 +00:00
}
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(newfires); i++) {
cell* c = newfires[i].first;
int qty = newfires[i].second;
qty /= 2;
2018-12-16 23:04:59 +00:00
if(c->wall == waExplosiveBarrel) explodeBarrel(c);
else if(c->wall == waFireTrap) {
if(c->wparam == 0) c->wparam = 1;
continue;
}
else if(c->wall == waBonfireOff) activateActiv(c, false);
else if(cellHalfvine(c)) destroyHalfvine(c, waPartialFire, 6);
else makeflame(c, qty, false);
if(c->wparam < qty) c->wparam = qty;
offscreen2.push_back(c);
if(c->land == laRose || c->land == laWildWest || c->land == laOvergrown || isHaunted(c->land) || c->land == laMountain || c->land == laIce) {
for(int j=c->mpdist-1; j>=7; j--) setdist(c, j, NULL);
}
2016-08-26 09:58:03 +00:00
}
offscreen_fire = move(offscreen2);
}
EX }
2016-08-26 09:58:03 +00:00
bool gardener = false;
EX void livecaves() {
vector<cell*>& allcells = currentmap->allcells();
2018-06-22 12:47:24 +00:00
int dcs = isize(allcells);
2016-08-26 09:58:03 +00:00
vector<cell*> bringlife;
int gr = gamerange();
2016-08-26 09:58:03 +00:00
std::vector<int> heatvals(dcs);
2016-08-26 09:58:03 +00:00
for(int i=0; i<dcs; i++) {
2017-03-23 10:53:57 +00:00
cell *c = allcells[i];
if(!doall && c->cpdist > gr+1) break;
2016-08-26 09:58:03 +00:00
int & hv = heatvals[i];
2017-03-23 10:53:57 +00:00
if(c->wall == waCavefloor || c->wall == waCavewall || c->wall == waDeadTroll) {
hv = 0;
2016-08-26 09:58:03 +00:00
if(c->monst == moDarkTroll) c->monst = moTroll;
if(c->item || c->monst || c->cpdist == 0) continue;
for(cell *c2: adj_minefield_cells(c)) {
eWall w = c2->wall;
if(w == waDeadfloor) hv++, bringlife.push_back(c2);
else if(w == waDeadwall || (w == waDeadfloor2 && !c2->monst))
hv--, bringlife.push_back(c2);
else if(w == waCavefloor) hv++;
else if(w == waCavewall) hv--;
else if(w == waRubble) hv--;
else if(w == waGargoyle) hv--;
else if(w == waGargoyleFloor) hv--;
else if(w == waGargoyleBridge) hv--;
else if(w == waStone) ;
else if(w == waDeadTroll) hv -= 5;
else if(w == waDeadTroll2) hv -= 3;
else if(w == waPetrified || w == waPetrifiedBridge) hv -= 2;
else if(w == waVinePlant) hv--;
else if(chaosmode && c2->land != laCaves && c2->land != laEmerald) ;
else if(c2->land == laTrollheim) ; // trollheim floor does not count
else if(w != waBarrier) hv += 5;
2017-03-23 10:53:57 +00:00
if(sword::at(c)) hv += 500;
2016-08-26 09:58:03 +00:00
if(c2->cpdist == 0 && markOrb(itOrbDigging)) hv+=100;
if(items[itOrbEmpathy] && isFriendly(c2) && markEmpathy(itOrbDigging))
hv+=100;
if(w == waThumperOn) hv+=100;
if(w == waFire) hv+=100;
if(w == waBigStatue) hv-=100;
if(c2->item && !peace::on) hv+=2;
if(c2->monst == moZombie) hv += 10;
if(c2->monst == moGhost) hv += 10;
if(c2->monst == moTentacleGhost) hv += 10;
if(c2->monst == moFriendlyGhost) hv += 10;
if(c2->monst == moSkeleton) hv ++;
if(c2->monst == moGargoyle) hv--;
if(c2->monst == moDraugr) hv--;
if(isDragon(c2->monst)) hv++;
if(c2->monst == moNecromancer) hv += 10;
if(c2->monst == moWormtail) hv++;
if(c2->monst == moTentacletail) hv-=2;
if(isIvy(c2)) hv--;
if(isDemon(c2)) hv-=3;
// if(c2->monst) c->tmp++;
// if(c2->monst == moTroll) c->tmp -= 3;
2016-08-26 09:58:03 +00:00
}
}
else if(c->land == laLivefjord) {
hv = 0;
2016-08-26 09:58:03 +00:00
if(c->monst == moWaterElemental)
hv += 1000;
2016-08-26 09:58:03 +00:00
if(isPlayerInBoatOn(c) && markOrb(itOrbWater))
hv += 1000;
2016-08-26 09:58:03 +00:00
if(c->monst == moEarthElemental)
hv -= 1000;
2016-08-26 09:58:03 +00:00
if(isPlayerOn(c) && markOrb(itOrbDigging))
hv -= 1000;
for(int j=0; j<c->type; j++) if(c->move(j)) {
cell *c2 = c->move(j);
2016-08-26 09:58:03 +00:00
if(c2->wall == waNone || c2->wall == waStrandedBoat)
hv -= (c2->land == laLivefjord ? 1 : 100);
if(c2->wall == waTempFloor || c2->wall == waTempBridge || c2->wall == waTempBridgeBlocked)
2016-08-26 09:58:03 +00:00
;
else if(c2->wall == waDeadTroll || c2->wall == waDeadTroll2 || c2->wall == waThumperOn || isFire(c2) || snakelevel(c2))
hv -= 10;
else if(c2->wall == waPetrified || c2->wall == waPetrifiedBridge)
hv -= 10;
2016-08-26 09:58:03 +00:00
if(c2->wall == waBigStatue)
hv -= 10;
2016-08-26 09:58:03 +00:00
if(c2->wall == waSea || c2->wall == waBoat)
hv += (c2->land == laLivefjord ? 1 : 100);
2016-08-26 09:58:03 +00:00
if(c2->monst == moWaterElemental)
hv += 1000;
2017-03-23 10:53:57 +00:00
if(isPlayerOn(c2) && c2->wall == waBoat && markOrb(itOrbWater))
hv += 1000;
2016-08-26 09:58:03 +00:00
if(c2->monst == moEarthElemental)
hv -= 1000;
2017-03-23 10:53:57 +00:00
if(isPlayerOn(c2) && markOrb(itOrbDigging))
hv -= 1000;
2016-08-26 09:58:03 +00:00
if(items[itOrbEmpathy] && isFriendly(c2) && markEmpathy(itOrbDigging))
hv -= 1000;
2016-08-26 09:58:03 +00:00
if(c2->wall == waBarrier) {
bool landbar = false;
for(int k=0; k<c2->type; k++)
if(c2->move(k)) {
cell *c3 = c2->move(k);
2016-08-26 09:58:03 +00:00
if(!isSealand(c3->land))
landbar = true;
}
if(landbar) hv -= 5;
else hv += 5;
2016-08-26 09:58:03 +00:00
}
}
}
}
for(int i=0; i<dcs; i++) {
2017-03-23 10:53:57 +00:00
cell *c = allcells[i];
if(!doall && c->cpdist > gr+1) break;
int hv = heatvals[i];
2017-03-23 10:53:57 +00:00
2016-08-26 09:58:03 +00:00
if(c->wall == waCavefloor || c->wall == waCavewall) {
// if(c->land != laCaves) continue;
// if(c->wall == waThumper || c->wall == waBonfire) continue;
if(hv > 0) c->wall = waCavefloor;
if(hv < 0) {
2016-08-26 09:58:03 +00:00
c->wall = waCavewall;
if(c->land != laCaves && c->land != laDeadCaves && c->land != laEmerald && !gardener) {
gardener = true;
achievement_gain_once("GARDENER");
2016-08-26 09:58:03 +00:00
}
}
}
else if(c->land == laLivefjord) {
if(hv > 0 && c->wall == waStrandedBoat) c->wall = waBoat;
if(hv > 0 && c->wall == waNone) {
2016-08-26 09:58:03 +00:00
if(c->item && c->cpdist == 1 && markOrb(itOrbWater))
collectItem(c);
c->wall = waSea;
}
if(hv < 0 && c->wall == waBoat) c->wall = waStrandedBoat;
if(hv < 0 && c->wall == waSea) c->wall = waNone;
}
2016-08-26 09:58:03 +00:00
}
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(bringlife); i++) {
2016-08-26 09:58:03 +00:00
cell *c = bringlife[i];
if(c->land == laDeadCaves)
achievement_gain_once("LIFEBRINGER");
2016-08-26 09:58:03 +00:00
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 && !inv::on) c->item = itOrbLife;
2016-08-26 09:58:03 +00:00
if(c->monst == moEarthElemental) {
addMessage(XLAT("%The1 is destroyed by the forces of Life!", c->monst));
2017-03-23 10:53:57 +00:00
fallMonster(c);
2016-08-26 09:58:03 +00:00
c->item = itOrbDigging;
}
}
}
/* evolver */
2019-08-09 23:31:44 +00:00
EX namespace tortoise {
EX map<cell*, int> emap;
EX map<cell*, int> babymap;
EX int last;
2016-08-26 09:58:03 +00:00
#if HDR
2016-08-26 09:58:03 +00:00
enum tflag {
tfShell, tfScute0, tfScute1, tfScute2, tfScute3,
tfEdge1, tfEdge, tfEdge3,
tfLongNeck, tfFront, tfRear, tfTail,
tfEyeHue, tfShellHue, tfScuteHue, tfSkinHue,
tfShellSat, tfScuteSat, tfSkinSat,
tfShellDark, tfSkinDark,
tfCOUNT
};
#endif
2016-08-26 09:58:03 +00:00
EX const int numbits = (int) tfCOUNT;
EX const int mask = (1<<numbits)-1;
2016-08-26 09:58:03 +00:00
EX int getb(cell *where) {
2016-08-26 09:58:03 +00:00
if(emap.count(where)) return emap[where];
2020-03-02 16:53:13 +00:00
return getBits(where) & ((1<<numbits)-1);
2016-08-26 09:58:03 +00:00
}
EX int countBits(int c) {
2016-08-26 09:58:03 +00:00
int bi = 0;
for(int i=0; i<numbits; i++) if((c >> i)&1) bi++;
return bi;
}
EX int getBit(int bits, int id) { return (bits >> id) & 1; }
2016-08-26 09:58:03 +00:00
2019-08-09 23:31:44 +00:00
EX int getRandomBits() { return hrand(1 << numbits); }
2016-08-26 09:58:03 +00:00
EX bool seek() { return items[itBabyTortoise] % 5; }
2019-08-09 23:31:44 +00:00
EX int seekbits;
2016-08-26 09:58:03 +00:00
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<val-d) val -= d;
else val = target;
}
2020-03-22 08:46:13 +00:00
EX bool shading_enabled = true;
EX bool shading_on() {
return shading_enabled && seek();
}
EX void updateVals(int delta) {
int currbits = getBits(cwt.at);
2016-08-26 09:58:03 +00:00
for(int i=0; i<numbits; i++)
2020-03-22 08:46:13 +00:00
update(seekval[i], shading_on() ? getBit(seekbits, i) : .5, delta);
2016-08-26 09:58:03 +00:00
for(int i=0; i<numbits; i++)
update(currval[i], getBit(currbits, i), delta);
}
EX double getScent(int bits) {
2016-08-26 09:58:03 +00:00
double res = 0;
for(int i=0; i<numbits; i++)
/* if(getBit(bits, i) != getBit(getBits(cwt.at), i))
2016-08-26 09:58:03 +00:00
res += (1 - 2*getBit(bits, i)); */
res += (2* seekval[i] - 1) * (getBit(bits, i) - currval[i]);
// seek curr bit => 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;
}
EX int diff(int bits) { return countBits(bits ^ tortoise::seekbits); }
2016-08-26 09:58:03 +00:00
int progress(int bits) { return numbits - diff(bits); }
EX string measure(int bits) {
2016-08-26 09:58:03 +00:00
return "(" + its(progress(bits)) + "/" + its(tortoise::numbits) + ")";
}
template<class T> void swap_data(T& data, cell *c1, cell *c2) {
changes.map_value(data, c1);
changes.map_value(data, c2);
if(data.count(c1) && data.count(c2))
swap(data[c1], data[c2]);
else if(data.count(c1))
data[c2] = data[c1], data.erase(c1);
else if(data.count(c2))
data[c1] = data[c2], data.erase(c2);
}
EX void move_baby(cell *c1, cell *c2) {
swap_data(babymap, c1, c2);
}
EX void move_adult(cell *c1, cell *c2) {
swap_data(emap, c1, c2);
}
2019-08-09 23:31:44 +00:00
EX }
2016-08-26 09:58:03 +00:00
EX namespace dragon {
2016-08-26 09:58:03 +00:00
EX int whichturn; // which turn has the target been set on
EX cell *target; // actually for all Orb of Control
2016-08-26 09:58:03 +00:00
void pullback(cell *c) {
int maxlen = iteration_limit;
2016-08-26 09:58:03 +00:00
while(maxlen-->0) {
2019-11-22 17:48:51 +00:00
auto mi = moveimon(c).rev();
mountmove(mi, true);
c->monst = mi.s->monst;
c->hitpoints = mi.s->hitpoints;
animateMovement(mi, LAYER_BIG);
2016-08-26 09:58:03 +00:00
c->stuntime = 2;
2019-11-22 17:48:51 +00:00
if(mi.s->mondir == NODIR) { c->mondir = NODIR; mi.s->monst = moNone; return; }
c = mi.s;
2016-08-26 09:58:03 +00:00
}
}
2017-03-23 10:53:57 +00:00
bool dragbugs = false;
EX cell *findhead(cell *c) {
2016-08-26 09:58:03 +00:00
cell *cor = c;
int maxlen=iteration_limit;
2016-08-26 09:58:03 +00:00
findhead:
if(maxlen--<0) return c;
if(c->monst == moDragonHead) return c;
for(int i=0; i<c->type; i++)
if(c->move(i) && isDragon(c->move(i)->monst) && c->move(i)->mondir == c->c.spin(i)) {
c = c->move(i); goto findhead;
2016-08-26 09:58:03 +00:00
}
if(cmode & sm::MAP) return c;
if(!history::includeHistory) {
printf("dragon bug #3 (%p -> %p)\n", hr::voidp(cor), hr::voidp(c));
dragbugs = true;
}
2017-03-23 10:53:57 +00:00
c->monst = moDragonHead; return c;
}
void validate(const char *where) {
dragbugs = false;
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(dcal); i++)
2017-03-23 10:53:57 +00:00
if(dcal[i]->monst == moDragonTail)
findhead(dcal[i]);
if(dragbugs) {
printf("DRAGON BUG in %s\n", where);
exit(1);
}
2016-08-26 09:58:03 +00:00
}
EX int bodypart(cell *c, cell *head) {
2016-08-26 09:58:03 +00:00
int i = 0, j = 0;
int maxlen = iteration_limit;
2016-08-26 09:58:03 +00:00
while(maxlen-->0) {
if(head == c) i = j;
j++;
if(head->mondir == NODIR) break;
head = head->move(head->mondir);
2016-08-26 09:58:03 +00:00
}
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;
}
EX void kill(cell *c, eMonster who) {
2016-08-26 09:58:03 +00:00
int delay = false;
kills[moDragonHead]++;
int penalty = 0;
int maxlen = iteration_limit;
2016-08-26 09:58:03 +00:00
while(maxlen-->0) {
2020-02-29 16:58:59 +00:00
changes.ccell(c);
2016-08-26 09:58:03 +00:00
makeflame(c, 5, false);
2017-03-23 10:53:57 +00:00
eMonster m = c->monst;
drawFireParticles(c, 16);
2016-08-26 09:58:03 +00:00
c->monst = moNone;
2017-03-23 10:53:57 +00:00
if(checkOrb(who, itOrbUndeath))
c->monst = moFriendlyGhost;
if(checkOrb(who, itOrbStone))
c->wparam = m, c->wall = waPetrified;
else if(c->wall == waFire) {
2016-08-26 09:58:03 +00:00
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->move(c->mondir);
2016-08-26 09:58:03 +00:00
}
}
EX int totalhp(cell *c) {
2016-08-26 09:58:03 +00:00
int total = 0;
int maxlen = iteration_limit;
2016-08-26 09:58:03 +00:00
while(maxlen-->0) {
if(!isDragon(c->monst)) {
if(!history::includeHistory) printf("dragon bug #4\n");
2016-08-26 09:58:03 +00:00
return total;
}
total += c->hitpoints;
if(c->mondir == NODIR) return total;
c = c->move(c->mondir);
2016-08-26 09:58:03 +00:00
}
return total;
}
#define SWAPBITFIELD(x,y,t) { t bak=x; x=y; y=bak; }
void pullfront(cell *c, cell *until) {
int maxlen = iteration_limit;
2017-03-23 10:53:57 +00:00
static vector<cell*> allcells;
allcells.clear();
2016-08-26 09:58:03 +00:00
while(maxlen-->0) {
2017-03-23 10:53:57 +00:00
allcells.push_back(c);
// SWAPBITFIELD(c->monst, buffer->monst, eMonster);
// SWAPBITFIELD(c->hitpoints, buffer->hitpoints, int);
2016-08-26 09:58:03 +00:00
c->stuntime = 2;
if(c == until) {
2018-06-22 12:47:24 +00:00
for(int i=isize(allcells)-2; i>=0; i--) {
2017-03-23 10:53:57 +00:00
cell *cmt = allcells[i+1];
cell *cft = allcells[i];
cmt->hitpoints = cft->hitpoints;
cmt->monst = cft->monst;
cft->monst = moNone;
2019-11-22 17:48:51 +00:00
auto mi = moveimon(cft);
mountmove(mi, true);
animateMovement(mi, LAYER_BIG);
2017-03-23 10:53:57 +00:00
}
while(c->mondir != NODIR) {
c = c->move(c->mondir);
2016-08-26 09:58:03 +00:00
c->stuntime = 2;
}
2017-03-23 10:53:57 +00:00
break;
2016-08-26 09:58:03 +00:00
}
if(c->mondir == NODIR) { printf("dragon bug\n"); break; }
c = c->move(c->mondir);
if(!c) {
if(!history::includeHistory) printf("dragon bug #2\n");
break;
}
2016-08-26 09:58:03 +00:00
}
}
EX bool move(const movei& mi) {
auto& df = mi.s;
auto& dt = mi.t;
2016-08-26 09:58:03 +00:00
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->move(df->mondir);
2016-08-26 09:58:03 +00:00
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;
}
EX }
2017-03-23 10:53:57 +00:00
2019-08-09 23:31:44 +00:00
EX namespace sword {
#if HDR
struct sworddir {
int angle;
transmatrix T;
};
2019-08-19 08:55:02 +00:00
/** dimensions available to the Sword */
#define SWORDDIM (hybri ? 2 : WDIM)
2019-08-19 08:55:02 +00:00
2019-08-09 23:31:44 +00:00
#endif
EX int sword_angles;
2019-08-09 23:31:44 +00:00
EX array<sworddir, MAXPLAYER> dir;
2017-03-23 10:53:57 +00:00
2019-05-28 23:01:13 +00:00
void possible_divisor(int s) { sword_angles *= s / gcd(sword_angles, s); }
EX void determine_sword_angles() {
2019-05-28 23:01:13 +00:00
sword_angles = 2;
2019-08-19 08:55:02 +00:00
if(SWORDDIM == 3) sword_angles = 1;
2020-10-15 14:33:52 +00:00
#if CAP_IRR
else if(IRREGULAR) sword_angles = 840;
2020-10-15 14:33:52 +00:00
#endif
#if CAP_BT
else if(bt::in()) sword_angles = 42;
2020-10-15 14:33:52 +00:00
#endif
#if CAP_ARCM
else if(arcm::in()) {
2019-05-28 23:01:13 +00:00
if(!PURE) possible_divisor((BITRUNCATED ? 2 : 1) * isize(arcm::current.faces));
if(!DUAL) for(int f: arcm::current.faces) possible_divisor(f);
}
2020-10-15 14:33:52 +00:00
#endif
2019-05-28 23:01:13 +00:00
else {
possible_divisor(S7);
if(BITRUNCATED) possible_divisor(S3);
}
}
EX cell *pos2(cell *c, int s) {
2017-03-23 10:53:57 +00:00
int t = c->type;
if(hybri) t -= 2;
2017-03-23 10:53:57 +00:00
s *= 2;
s += sword_angles/t;
s %= (2 * sword_angles);
if(s<0) s += sword_angles * 2;
2018-07-16 19:41:59 +00:00
s *= t;
s /= (sword_angles * 2);
return c->move(s);
2017-03-23 10:53:57 +00:00
}
2019-08-09 23:31:44 +00:00
EX cell *pos(cell *c, const sworddir& sd, bool rev) {
2019-08-19 08:55:02 +00:00
if(SWORDDIM == 2)
return pos2(c, sd.angle + (rev ? sword_angles/2 : 0));
else {
cell *best = NULL;
ld bdist = HUGE_VAL;
for(int i=0; i<S7; i++) {
2019-11-26 23:39:41 +00:00
ld dist = hdist(sd.T * xpush0(rev?-0.1:0.1), tC0(currentmap->relative_matrix(c->move(i)->master, c->master, C0)));
if(dist < bdist) bdist = dist, best = c->move(i);
}
return best;
}
}
2017-03-23 10:53:57 +00:00
eItem orbof(bool rev) { return rev ? itOrbSword2 : itOrbSword; }
EX int orbcount(bool rev) { return items[orbof(rev)]; }
2017-03-23 10:53:57 +00:00
EX cell *pos(int id, bool rev) {
2017-03-23 10:53:57 +00:00
if(!orbcount(rev)) return NULL;
return pos(playerpos(id), dir[id], rev);
2017-03-23 10:53:57 +00:00
}
2019-08-09 23:31:44 +00:00
EX bool at(cell *where, bool noplayer IS(false)) {
2017-03-23 10:53:57 +00:00
if(noplayer) return false;
if(!orbcount(0) && !orbcount(1)) return false;
for(int i=0; i<numplayers(); i++) if(multi::playerActive(i)) for(int b=0; b<2; b++)
if(pos(i,b) == where) return true;
return false;
}
EX bool isnear(cell *where) {
2017-03-23 10:53:57 +00:00
if(at(where, false)) return true;
forCellEx(w2, where) if(at(w2, false)) return true;
return false;
}
// from c1 to c2
2019-08-09 23:31:44 +00:00
EX sworddir shift(cell *c1, cell *c2, sworddir d) {
if(!c1 || !c2) return d;
2017-03-23 10:53:57 +00:00
int s1 = neighborId(c1, c2);
int s2 = neighborId(c2, c1);
if(s1 < 0 || s2 < 0) return d;
2019-08-19 08:55:02 +00:00
if(SWORDDIM == 2) {
int sub = (hybri) ? 2 : 0;
2019-08-19 08:55:02 +00:00
int t2 = c2->type - sub;
int t1 = c1->type - sub;
if(c1->c.mirror(s1))
2019-08-19 08:55:02 +00:00
d.angle = ((s2*sword_angles/t2 - d.angle + s1*sword_angles/t1) + sword_angles/2) % sword_angles;
else
2019-08-19 08:55:02 +00:00
d.angle = ((s2*sword_angles/t2 - s1*sword_angles/t1) + sword_angles/2 + d.angle) % sword_angles;
}
else {
2019-11-26 23:39:41 +00:00
transmatrix T = currentmap->relative_matrix(c1->master, c2->master, C0);
T = gpushxto0(tC0(T)) * T;
d.T = T * d.T;
fixmatrix(d.T);
}
return d;
2017-03-23 10:53:57 +00:00
}
transmatrix initial_matrix() {
if(S7 == 6) return Id;
return cspin(0, 1, 0.1) * cspin(0, 2, 0.1) * cspin(1, 2, 0.1) * Id;
}
EX sworddir initial(cell *c) {
sworddir res;
res.angle = (sword::sword_angles / cwt.at->type + 1) / 2;
2019-08-19 08:55:02 +00:00
if(SWORDDIM == 3) res.T = initial_matrix();
return res;
}
2017-03-23 10:53:57 +00:00
void shuffle(int i) {
changes.value_keep(dir[i].angle);
changes.value_keep(dir[i].T);
dir[i].angle = hrand(sword_angles);
2019-08-19 08:55:02 +00:00
if(SWORDDIM == 3) dir[i].T = initial_matrix();
}
EX void reset() {
items[itOrbSword] = items[itOrbSword2] = 0;
shuffle(multi::cpid);
}
2017-03-23 10:53:57 +00:00
void shuffle() {
for(int i=0; i<MAXPLAYER; i++) shuffle(i);
2017-03-23 10:53:57 +00:00
}
2019-08-09 23:31:44 +00:00
EX }
2017-03-23 10:53:57 +00:00
EX namespace kraken {
2017-03-23 10:53:57 +00:00
EX cell *head(cell *c) {
2017-03-23 10:53:57 +00:00
if(c->monst == moKrakenH) return c;
if(c->monst == moKrakenT) return c->move(c->mondir);
2017-03-23 10:53:57 +00:00
return NULL;
}
EX void kill(cell *c, eMonster who) {
2020-03-02 18:51:55 +00:00
changes.ccell(c);
2017-03-23 10:53:57 +00:00
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;
2020-02-29 16:58:59 +00:00
forCellEx(c1, c)
if(c1->monst == moKrakenT) {
changes.ccell(c1);
2017-03-23 10:53:57 +00:00
drawParticles(c, minf[moKrakenT].color, 16);
2020-02-29 16:58:59 +00:00
c1->monst = moNone;
2017-03-23 10:53:57 +00:00
if(checkOrb(who, itOrbStone)) {
2020-02-29 16:58:59 +00:00
if(isWatery(c1))
c1->wall = waNone;
2017-03-23 10:53:57 +00:00
else
2020-02-29 16:58:59 +00:00
c1->wall = waPetrified, c1->wparam = moKrakenT;
2017-03-23 10:53:57 +00:00
}
}
}
EX int totalhp(cell *c) {
2017-03-23 10:53:57 +00:00
int total = 0;
2020-02-29 16:58:59 +00:00
forCellEx(c1, c)
if(c1->monst == moKrakenT)
total += c1->hitpoints;
2017-03-23 10:53:57 +00:00
return total;
}
void trymove(cell *c);
void sleep(cell *c) {
if(c->monst == moKrakenT) c = c->move(c->mondir);
2017-03-23 10:53:57 +00:00
c->stuntime = 1;
forCellEx(c2, c) c2->stuntime = 1;
}
EX void attacks() {
pathdata pd(2);
2017-03-23 10:53:57 +00:00
bool offboat[MAXPLAYER];
for(int i=0; i<MAXPLAYER; i++) offboat[i] = false;
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(dcal); i++) {
2017-03-23 10:53:57 +00:00
cell *c = dcal[i];
if(c->monst == moKrakenT && !c->stuntime) forCellEx(c2, c) {
bool dboat = false;
if(c2->monst && canAttack(c, moKrakenT, c2, c2->monst, AF_ONLY_FBUG)) {
attackMonster(c2, AF_NORMAL | AF_MSG, c->monst);
2017-03-23 10:53:57 +00:00
sleep(c);
}
else for(int i=0; i<numplayers(); i++) if(playerpos(i) == c2) {
if(isPlayerInBoatOn(c2, i)) {
addMessage(XLAT("%The1 destroys your boat!", moKrakenH));
dboat = true;
offboat[i] = true;
}
else if(offboat[i]) ;
else killThePlayer(moKrakenH, i, 0);
sleep(c);
}
if(dboat) destroyBoats(c2, c, true);
}
}
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(dcal); i++) {
2017-03-23 10:53:57 +00:00
cell *c = dcal[i];
if(c->monst == moKrakenH && !c->stuntime && !isWateryOrBoat(c)) {
2020-01-18 15:03:32 +00:00
vector<cell*> ctab;
forCellEx(c2, c) if(isWatery(c2)) ctab.push_back(c2);
hrandom_shuffle(ctab);
for(auto& cc: ctab) trymove(cc);
2017-03-23 10:53:57 +00:00
}
}
}
// c is the tentacle which will be the head after the move
EX void trymove(cell *c) {
if(kraken_pseudohept(c)) return;
cell *c2 = c->move(c->mondir);
2017-03-23 10:53:57 +00:00
if(!isWatery(c)) return;
if(againstCurrent(c, c2)) return;
forCellIdEx(c3, i, c) {
2019-05-24 21:49:52 +00:00
if(c3->monst && c3 != c2 && !(c3->mondir < c3->type &&
c3->move(c3->mondir) == c2))
2017-03-23 10:53:57 +00:00
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<pair<cell*, cell*> > acells;
acells.push_back(make_pair(c2, c));
forCellIdEx(c3, i, c) {
c3->monst = moKrakenT, c3->mondir = c->c.spin(i), c3->monmirror = c->monmirror ^ c->c.mirror(i), onpath(c3, 0);
int i0 = (i+c->c.spin(c->mondir)-c->mondir+96+c->type/2) % c2->type;
2017-03-23 10:53:57 +00:00
c3->hitpoints = hpcount[i0];
acells.push_back(make_pair(c2->move(i0), c3));
2017-03-23 10:53:57 +00:00
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;
}
}
2018-06-22 12:47:24 +00:00
while(isize(acells)) {
2017-03-23 10:53:57 +00:00
// bool found = false;
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(acells); i++) {
2017-03-23 10:53:57 +00:00
/* bool noconflict = true;
2018-06-22 12:47:24 +00:00
for(int j=0; j<isize(acells); j++)
2017-03-23 10:53:57 +00:00
if(acells[i].second == acells[j].first)
noconflict = false; */
/* if(noconflict) */ {
// found = true;
indAnimateMovement(match(acells[i].first, acells[i].second), LAYER_BIG);
2018-06-22 12:47:24 +00:00
acells[i] = acells[isize(acells)-1];
acells.resize(isize(acells)-1);
2017-03-23 10:53:57 +00:00
i--;
}
}
}
commitAnimations(LAYER_BIG);
sleep(c);
onpath(c, 0);
2017-03-23 10:53:57 +00:00
return;
}
EX }
2017-03-23 10:53:57 +00:00
bool barrierhept(cell *c) {
return c->bardir != NOBARRIERS && c->bardir != NODIR;
}
2019-02-17 17:33:15 +00:00
#if CAP_FIELD
EX namespace prairie {
2017-03-23 10:53:57 +00:00
using namespace fieldpattern;
int getfval(cell *c3) {
if(barrierhept(c3)) return btspin(fieldval(c3).first, c3->bardir)+1;
return 0;
}
EX void spread(cell *c, cell *from) {
2017-03-23 10:53:57 +00:00
int rd;
c->LHU.fi.flowerdist = 8;
c->LHU.fi.walldist = 8;
c->LHU.fi.walldist2 = 8;
2019-06-01 18:02:33 +00:00
if(chaosmode) {
c->LHU.fi.rval = 0;
}
2019-11-27 00:01:20 +00:00
else if(quotient) { // fix cylinder
2017-06-09 01:41:33 +00:00
c->LHU.fi.rval = 0;
}
else if(euclid) {
auto co = euc2_coordinates(c);
int y = co.second;
2017-03-23 10:53:57 +00:00
c->LHU.fi.rval = (y&15);
}
else if(sphere) {
c->LHU.fi.rval = celldistance(c, cwt.at) + 8 - (PURE ? 2 : 3);
2017-03-23 10:53:57 +00:00
}
else if(weirdhyperbolic) {
c->LHU.fi.rval = max(celldist(c), 15);
}
2017-03-23 10:53:57 +00:00
else {
if(geometry == gFieldQuotient || !from) {
2017-11-03 21:19:45 +00:00
c->fval = currfp.distflower0;
2017-03-23 10:53:57 +00:00
}
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", hr::voidp(c), barclose);
2017-03-23 10:53:57 +00:00
raiseBuggyGeneration(c, "could not set river fval");
}
}
pair<int,bool> fv = fieldpattern::fieldval(c);
fv = currfp.gmul(fv, currfp.inverses[c->fval-1]);
2017-03-23 10:53:57 +00:00
rd = currfp.getdist(fv, currfp.distriver);
int rl = currfp.getdist(fv, currfp.distriverleft);
int rr = currfp.getdist(fv, currfp.distriverright);
2017-03-23 10:53:57 +00:00
c->LHU.fi.flowerdist = currfp.getdist(fv, currfp.distflower);
c->LHU.fi.walldist = currfp.getdist(fv, currfp.distwall);
c->LHU.fi.walldist2 = currfp.getdist(fv, currfp.distwall2);
2017-03-23 10:53:57 +00:00
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 = currfp.markers[fieldpattern::btspin(c->master->fieldval,i)];
2017-03-23 10:53:57 +00:00
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?(PURE?7:6):PURE?4:2)
#define RHIGH (sphere?(PURE?8:9):PURE?11:13)
2017-03-23 10:53:57 +00:00
EX bool no_worms(cell *c) {
if(c->land != laPrairie) return false;
int rv = c->LHU.fi.rval;
return rv > RLOW+1 && rv < RHIGH-1;
}
EX bool isriver(cell *c) {
2017-03-23 10:53:57 +00:00
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;
}
EX bool nearriver(cell *c) {
2017-03-23 10:53:57 +00:00
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;
}
EX cell *next(cell *c, int pv IS(1)) {
2017-03-23 10:53:57 +00:00
for(int i=0; i<c->type; 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;
}
}
EX vector<cell*> beaststogen;
2017-03-23 10:53:57 +00:00
EX void generateBeast(cell *c) {
2017-03-23 10:53:57 +00:00
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);
}
2018-06-28 11:35:03 +00:00
void moveAt(cell *c, manual_celllister& cl) {
if(!cl.add(c)) return;
2017-03-23 10:53:57 +00:00
vector<cell*> whirlline;
whirlline.push_back(c);
cell *c2 = prev(c);
2018-07-03 02:18:42 +00:00
while(c2 && cl.add(c2)) {
whirlline.push_back(c2);
2017-03-23 10:53:57 +00:00
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 && cl.add(c2)) whirlline.push_back(c2), c2 = next(c2);
2018-06-22 12:47:24 +00:00
int qty = isize(whirlline);
2017-03-23 10:53:57 +00:00
if(shmup::on) {
for(int i=0; i<qty; i++) if(whirlline[i]->cpdist <= gamerange()) {
2017-03-23 10:53:57 +00:00
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) {
cp->mondir = neighborId(cp, cn);
beastAttack(cp, true, true);
2017-03-23 10:53:57 +00:00
if(!cn->monst && !isPlayerOn(cn) && passable_for(cp->monst, cn, cp, P_DEADLY))
2019-11-22 17:19:22 +00:00
moveMonster(movei(cp, cn, TELEPORT));
2017-03-23 10:53:57 +00:00
else {
playSound(NULL, "hit-axe"+pick123());
beastcrash(cn, cp);
cp->monst = moRagingBull;
cp->stuntime = 3;
cp->mondir = NODIR;
}
}
}
if(!sphere && !quotient) generateBeast(whirlline[0]);
}
}
EX void move() {
2019-05-27 05:17:39 +00:00
if(chaosmode) return;
2018-06-28 11:35:03 +00:00
manual_celllister cl;
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(dcal); i++) {
2017-03-23 10:53:57 +00:00
cell *c = dcal[i];
if(isriver(c)) moveAt(c, cl);
2017-03-23 10:53:57 +00:00
}
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(beaststogen); i++)
2017-03-23 10:53:57 +00:00
generateBeast(beaststogen[i]);
beaststogen.clear();
}
vector<cell*> tchoices;
cell *lasttreasure;
// vector<cell*> 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;
}
EX void generateTreasure(cell *c) {
2017-03-23 10:53:57 +00:00
// if(nearriver(c) && op
if(enter && nearriver(c) && opposite(c) && thisriver(c)) {
int hr = hrand(100);
if(hr == 0 && items[itGreenGrass] >= 10 && !inv::on) {
2017-03-23 10:53:57 +00:00
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);
}
}
EX void treasures() {
if(enter && !isriver(cwt.at)) enter = NULL;
else if(!enter && isriver(cwt.at)) enter = cwt.at;
2018-06-22 12:47:24 +00:00
if(isize(tchoices)) {
2017-03-23 10:53:57 +00:00
if(lasttreasure && lasttreasure->item == itGreenGrass) {
if(celldistance(lasttreasure, cwt.at) >= (PURE ? 7 : 10)) {
2017-03-23 10:53:57 +00:00
lasttreasure->item = itNone;
forCellEx(c2, lasttreasure) if(c2->item == itGreenGrass) c2->item = itNone;
}
else { tchoices.clear(); return; }
}
2018-06-22 12:47:24 +00:00
if(isize(tchoices) < 3) { tchoices.clear(); return; }
lasttreasure = tchoices[hrand(isize(tchoices))];
2017-03-23 10:53:57 +00:00
lasttreasure->item = itGreenGrass;
2018-06-22 12:47:24 +00:00
for(int i=0; i<isize(tchoices); i++) if(isNeighbor(tchoices[i], lasttreasure))
2017-03-23 10:53:57 +00:00
tchoices[i]->item = itGreenGrass;
tchoices.clear();
}
}
EX }
2019-02-17 17:33:15 +00:00
#else
namespace prairie {
bool no_worms(cell *c) { return false; }
void treasures() { }
void move() { }
bool isriver(cell *c) { return false; }
}
#endif
2017-03-23 10:53:57 +00:00
EX namespace ca {
EX ld prob = .2;
2019-12-07 10:12:46 +00:00
static const int MAX_NEIGHBOR = 60; /* may be larger than MAX_EDGE due to mineadj */
string carule[MAX_NEIGHBOR][2];
EX eWall wlive = waFloorA;
2020-04-12 18:33:53 +00:00
EX unordered_set<cell*> changed;
EX void list_adj(cell *c) {
changed.insert(c);
for(cell* c1: adj_minefield_cells(c))
changed.insert(c1);
}
2019-12-07 10:12:46 +00:00
// you can also do -mineadj
EX string fillup(string s) {
while(isize(s) < MAX_NEIGHBOR) s += '0';
return s;
}
2017-03-23 10:53:57 +00:00
EX void init() {
2017-03-23 10:53:57 +00:00
// hexagonal variant of Game of Life, as suggested by Wikipedia
2019-12-07 10:12:46 +00:00
for(int i=0; i<MAX_NEIGHBOR; i++)
carule[i][0] = fillup("00100"),
carule[i][1] = fillup("00011");
2017-03-23 10:53:57 +00:00
}
2017-07-22 23:33:27 +00:00
#if CAP_COMMANDLINE
2017-03-23 10:53:57 +00:00
bool readArg() {
using namespace arg;
if(argis("-caprob")) {
shift(); prob = argf();
return 0;
2017-03-23 10:53:57 +00:00
}
2019-12-07 10:12:46 +00:00
if(argis("-calive")) {
shift(); wlive = eWall(argi());
return 0;
}
2020-04-12 18:33:31 +00:00
if(argis("-carun")) {
shift(); int iter = argi();
start_game();
for(int i=0; i<iter; i++) simulate();
return 0;
}
if(args()[0] != '-') return 1;
if(args()[1] != 'c') return 1;
2017-03-23 10:53:57 +00:00
int livedead = args()[2] - '0';
if(livedead < 0 || livedead > 1) return 1;
2017-03-23 10:53:57 +00:00
int nei = -1;
if(args()[3]) {
2019-12-07 10:12:46 +00:00
nei = atoi(argcs()+3);
if(nei < 0 || nei >= MAX_NEIGHBOR) return 1;
2017-03-23 10:53:57 +00:00
}
shift();
2019-12-07 10:12:46 +00:00
string s = args();
if(nei == -1) for(int i=0; i<MAX_NEIGHBOR; i++)
carule[i][livedead] = fillup(s);
2017-03-23 10:53:57 +00:00
else
2019-12-07 10:12:46 +00:00
carule[nei][livedead] = fillup(s);
return 0;
2017-03-23 10:53:57 +00:00
}
auto ah = addHook(hooks_args, 0, readArg);
#endif
EX void simulate() {
if(cwt.at->land != laCA) return;
2020-04-12 18:38:17 +00:00
if(items[itOrbAether] < 2) items[itOrbAether] = 2;
2020-04-12 18:33:53 +00:00
vector<cell*> allcells;
for(cell *c: changed) allcells.push_back(c);
changed.clear();
2018-06-22 12:47:24 +00:00
int dcs = isize(allcells);
std::vector<bool> willlive(dcs);
2019-12-07 10:12:46 +00:00
int old = 0, xold = 0;
2017-03-23 10:53:57 +00:00
for(int i=0; i<dcs; i++) {
cell *c = allcells[i];
2020-04-12 18:33:53 +00:00
if(c->land != laCA) continue;
2017-03-23 10:53:57 +00:00
int nei = 0, live = 0;
2019-12-07 10:12:46 +00:00
for(cell *c2: adj_minefield_cells(c)) {
nei++; if(c2->wall == wlive) live++;
2017-03-23 10:53:57 +00:00
}
2019-12-07 10:12:46 +00:00
int welive = 0; if(c->wall == wlive) welive++;
2017-03-23 10:53:57 +00:00
willlive[i] = carule[nei][welive][live] == '1';
2019-12-07 10:12:46 +00:00
old += welive, xold += live;
2017-03-23 10:53:57 +00:00
}
for(int i=0; i<dcs; i++) {
cell *c = allcells[i];
2020-04-12 18:33:53 +00:00
auto last = c->wall;
2019-12-07 10:12:46 +00:00
c->wall = willlive[i] ? wlive : waNone;
2020-04-12 18:33:53 +00:00
if(c->wall != last) {
dynamicval<ld> d(prob, 0);
setdist(c, 7, nullptr);
list_adj(c);
}
2017-03-23 10:53:57 +00:00
}
2020-04-12 18:33:53 +00:00
println(hlog, make_tuple(dcs, old, xold, isize(changed)));
2017-03-23 10:53:57 +00:00
}
EX }
2017-07-10 18:47:38 +00:00
auto ccm = addHook(hooks_clearmemory, 0, [] () {
heat::offscreen_heat.clear();
heat::offscreen_fire.clear();
2017-07-10 18:47:38 +00:00
princess::clear();
2017-07-22 23:33:27 +00:00
mirror::mirrors.clear();
2017-07-10 18:47:38 +00:00
clearing::bpdata.clear();
2019-12-27 13:02:21 +00:00
clearing::stats.clear();
clearing::score.clear();
2017-07-10 18:47:38 +00:00
tortoise::emap.clear();
tortoise::babymap.clear();
2019-02-17 17:33:15 +00:00
#if CAP_FIELD
2017-07-10 18:47:38 +00:00
prairie::lasttreasure = NULL;
prairie::enter = NULL;
prairie::tchoices.clear();
prairie::beaststogen.clear();
2019-02-17 17:33:15 +00:00
#endif
2017-08-06 12:50:16 +00:00
mirror::clearcache();
2018-01-26 00:45:49 +00:00
}) +
2019-05-28 23:09:38 +00:00
addHook(hooks_gamedata, 0, [] (gamedata* gd) {
gd->store(heat::offscreen_heat);
gd->store(heat::offscreen_fire);
gd->store(princess::infos);
gd->store(mirror::mirrors);
gd->store(clearing::bpdata);
gd->store(tortoise::emap);
gd->store(tortoise::babymap);
gd->store(prairie::lasttreasure);
gd->store(prairie::enter);
gd->store(prairie::tchoices);
gd->store(prairie::beaststogen);
gd->store(sword::dir);
gd->store(elec::haveelec);
gd->store(elec::havecharge);
gd->store(elec::lightningfast);
gd->store(elec::havethunder);
gd->store(elec::afterOrb);
2019-05-28 23:09:38 +00:00
}) +
2018-01-26 00:45:49 +00:00
addHook(hooks_removecells, 0, [] () {
2020-01-02 15:50:15 +00:00
for(cell *c: removed_cells) clearing::score.erase(c);
2018-01-26 00:45:49 +00:00
eliminate_if(heat::offscreen_heat, is_cell_removed);
eliminate_if(heat::offscreen_fire, is_cell_removed);
eliminate_if(princess::infos, [] (princess::info*& i) {
if(is_cell_removed(i->princess) || is_cell_removed(i->prison)) {
2019-05-12 23:57:40 +00:00
DEBB(DF_MEMORY, ("removing a princess"))
2018-01-26 00:45:49 +00:00
if(i->princess && !is_cell_removed(i->princess)) {
2019-05-12 23:57:40 +00:00
DEBB(DF_MEMORY, ("faking a princess"))
2018-01-26 00:45:49 +00:00
princess::newFakeInfo(i->princess);
}
delete i;
return true;
}
return false;
});
2019-02-17 17:33:15 +00:00
#if CAP_FIELD
2018-01-26 00:45:49 +00:00
set_if_removed(prairie::lasttreasure, NULL);
set_if_removed(prairie::enter, NULL);
eliminate_if(prairie::tchoices, is_cell_removed);
eliminate_if(prairie::beaststogen, is_cell_removed);
2019-02-17 17:33:15 +00:00
#endif
2018-01-26 00:45:49 +00:00
for(auto& bpd: clearing::bpdata) set_if_removed(bpd.second.root, NULL);
});
2017-07-10 18:47:38 +00:00
2019-02-17 17:33:15 +00:00
#if CAP_FIELD
// windcode arrays precomputed for speed
int windcodes18920[] = {164,182,171,158,148,155,163,174,188,205,193,160,167,160,140,128,146,153,159,161,167,180,190,186,202,229,234,24,120,146,165,177,179,169,146,125,113,93,131,157,147,155,158,161,162,157,157,162,173,187,204,198,176,163,242,249,234,238,1,30,77,120,140,153,165,173,173,204,200,184,165,150,130,119,104,86,70,71,54,208,168,141,133,167,169,158,161,161,166,171,165,142,144,153,159,166,175,186,203,225,221,216,191,168,149,114,70,26,10,241,220,229,244,251,7,19,50,85,110,129,143,148,145,173,180,178,174,166,144,54,248,215,182,170,166,153,134,116,118,122,114,96,77,57,47,69,78,62,18,228,189,164,148,126,110,81,250,217,169,151,156,163,168,156,154,170,183,192,182,149,118,120,138,152,158,156,158,164,169,171,181,202,217,234,252,241,243,235,206,182,169,159,149,125,97,77,53,40,28,24,4,237,218,192,204,220,244,246,248,250,1,4,12,31,56,98,120,127,129,134,148,156,161,139,105,153,209,205,187,177,180,177,174,167,152,117,81,44,4,232,201,160,162,161,168,174,165,153,139,115,94,103,120,129,127,125,118,94,66,50,40,28,10,50,78,89,97,81,76,54,16,230,195,174,162,157,151,139,122,98,81,58,27,249,245,2,7,89,127,132,146,158,162,174,191,230,64,124,150,173,193,209,224,211,203,174,146,119,87,90,93,118,139,159,167,164,157,149,150,155,165,172,169,159,161,172,195,214,219,221,206,26,19,252,253,11,9,253,225,206,187,170,168,166,155,149,141,124,101,83,81,68,55,48,45,38,29,29,43,24,254,232,219,199,171,159,174,187,208,249,17,219,241,250,248,241,244,248,1,1,3,17,30,37,63,128,134,147,141,135,125,124,134,150,157,161,173,186,161,114,79,52,50,236,241,243,236,163,169,168,181,186,184,184,178,171,172,170,149,131,108,91,73,64,41,10,233,216,202,112,119,144,154,152,161,177,186,182,176,166,152,145,133,106,85,67,76,85,112,135,142,129,124,134,132,139,125,89,71,42,32,26,32,45,227,220,243,26,71,91,97,112,127,106,89,83,88,88,65,30,255,229,201,174,171,164,157,154,161,153,146,142,139,137,93,78,75,67,57,36,3,232,233,241,255,26,30,48,73,93,105,116,121,132,147,161,169,157,151,180,201,219,232,6,32,94,129,142,159,177,190,207,220,241,252,237,226,223,212,193,170,144,119,95,72,56,60,62,70,96,124,136,149,170,177,183,178,171,154,149,144,141,144,150,157,168,175,184,178,167,146,139,143,148,180,196,208,228,229,232,231,209,170,117,73,52,48,251,241,251,6,26,41,37,26,242,232,217,212,191,169,155,160,167,174,177,161,147,145,144,150,147,130,100,75,65,83,79,79,75,40,45,49,48,49,46,45,35,18,19,41,83,56,19,255,239,220,221,219,208,184,155,138,133,145,160,167,179,185,226,14,46,119,196,237,243,251,8,1,243,232,231,234,235,249,9,5,248,245,247,7,18,21,21,21,29,33,223,187,158,154,168,167,153,142,128,115,118,114,116,133,167,159,162,162,170,184,194,216,208,209,109,92,65,45,19,13,255,241,247,253,255,14,26,82,139,164,157,144,167,183,193,192,188,192,197,189,179,173,167,169,173,184,191,172,151,138,123,108,98,87,81,75,78,70,51,29,248,222,213,198,230,39,67,83,91,123,146,157,156,140,140,151,176,195,198,187,185,185,182,175,159,145,144,146,150,141,96,68,57,51,50,54,54,63,99,129,153,157,160,140,124,114,89,153,144,133,140,158,162,146,59,58,72,60,18,3,6,6,16,22,45,75,123,170,197,219,228,245,50,102,98,102,98,100,110,149,166,132,108,84,80,82,87,90,102,120,108,92,254,11,248,236,226,205,171,147,159,161,163,163,153,145,141,154,185,165,146,146,144,142,143,150,174,224,57,69,68,66,63,68,69,64,49,42,15,226,206,217,228,232,231,245,27,49,47,43,37,50,71,90,102,100,103,106,117,113,117,126,136,148,165,177,199,246,63,107,124,217,215,225,230,225,221,238,2,15,13,1,171,134,132,135,147,165,179,185,191,202,212,227,244,9,18,24,1,248,240,242,233,221,220,209,192,178,161,143,125,108,96,79,64,53,37,29,34,38,51,53,68,86,116,140,130,136,154,173,189,190,194,193,191,191,189,164,139,145,149,144,141,140,129,128,143,154,145,159,174,173,173,186,203,193,183,177,158,129,126,120,121,116,113,165,195,207,214,207,201,20,239,229,239,251,248,225,198,178,149,133,93,76,72,73,101,137,214,221,227,242,253,1,14,31,51,79,73,78,114,201,238,243,210,212,220,226,209,189,167,147,142,145,156,166,173,173,194,199,171,152,142,137,138,134,134,154,165,166,152,141,118,88,64,35,15,101,91,76,72,78,103,163,251,11,33,45,52,56,42,20,87,58,42,49,57,23,244,2,14,8,221,138,125,95,8
int windcodes5676[] = {152,138,172,172,141,158,157,124,119,130,125,143,190,206,205,187,171,175,136,86,111,80,0,210,177,155,125,96,80,66,162,135,121,119,150,136,86,91,93,193,201,212,203,213,227,230,243,228,210,186,138,68,242,217,216,172,145,199,31,43,57,82,110,119,95,49,31,4,13,17,254,198,173,161,182,204,108,133,122,87,88,103,64,47,44,29,26,255,235,192,133,122,156,154,84,67,68,129,169,191,181,182,140,91,49,51,62,52,76,79,52,19,241,207,175,208,225,235,225,202,207,193,192,235,236,236,224,242,250,237,252,23,13,250,231,230,217,194,193,209,173,105,76,52,39,11,246,236,233,245,240,1,246,206,177,151,91,98,170,212,207,238,5,22,29,34,43,44,45,57,74,80,85,142,153,157,129,126,110,52,13,27,44,41,2,241,248,5,11,18,34,58,59,37,16,216,191,168,172,169,165,167,137,127,231,222,228,251,24,47,81,113,147,163,142,122,82,38,59,80,81,109,154,156,55,33,36,19,42,43,30,16,12,15,24,33,20,13,2,3,6,5,252,231,191,139,91,90,82,103,158,195,205,223,229,57,90,55,42,16,20,30,37,72,128,161,184,207,200,213,220,204,198,206,212,213,185,153,132,103,75,49,23,21,23,24,50,63,53,40,25,25,45,77,92,96,75,69,54,25,23,23,21,2,243,236,220,180,133,134,162,212,241,238,247,236,246,252,250,249,234,196,169,203,221,220,206,162,168,161,106,11,247,241,246,253,242,213,17,235,193,193,240,1,13,22,0,241,228,215,238,10,29,53,31,53,47,12,9,254,234,232,235,249,234,235,228,218,205,167,182,199,208,174,255,247,237,151,184,16,74,73,59,38,23,46,76,10,31,24,252,232,240,253,252,5,222,199,228,9,13,253,241,242,3,42,27,1,12,21,242,213,177,159,186,191,167,112,58,28,47,46,67,119,196,222,238,231,219,195,195,191,16,249,250,3,12,15,18,22,34,31,24,26,41,44,39,45,44,36,23,45,54,44,43,50,88,115,38,53,38,64,105,158,192,191,194,161,189,186,161,152,91,158,144,155,162,111,71,28,1,230,234,7,28,21,28,46,88,76,42,1,224,238,227,233,224,235,251,8,4,2,4,9,26,22,1,20,54,58,57,83,97,120,83,38,31,32,36,109,195,200,202,208,216,130,148,166,191,168,169,180,169,158,161,168,182,197,158,125,131,93,52,34,17,17,250,231,231,243,230,218,235,47,30,16,25,30,21,41,55,96,104,102,113,133,180,196,216,185,142,113,145,150,108,105,63,34,253,250,13,46,63,73,111,85,74,54,77,103,159,197,219,133,191,216,243,16,26,14,15,21,28,38,205,244,32,48,58,44,44,52,40,22,247,254,5,251,8,19,16,255,4,25,35,43,51,34,1,12,20,15,9,255,247,1,17,16,25,4,10,18,14,12,45,17,245,239,228,220,174,150,124,94,87,41,51,68,80,52,45,80,89,104,87,249,196,205,223,224,237,246,240,228,218,211,3,32,62,154,137,79,47,22,24,24,25,6,237,229,234,7,19,32,30,26,12,19,36,85,117,158,175,180,173,157,244,221,220,234,222,204,196,209,233,239,242,232,220,223,196,183,208,206,231,234,224,203,221,237,233,221,209,179,160,138,137,142,132,118,91,76,78,71,56,31,19,9,2,11,14,17,11,3,0,249,19,35,37,80,79,65,53,51,39,26,22,19,12,5,13,16,19,22,45,66,113,102,103,113,135,104,73,60,63,82,65,74,90,50,21,19,1,3,18,24,17,19,35,38,48,249,240,252,244,224,246,253,244,247,223,206,179,138,92,118,139,89,120,126,162,214,243,252,3,3,2,253,236,210,45,25,194,226,255,3,2,254,1,8,5,5,5,14,245,240,248,234,148,161,120,121,180,224,219,230,236,233,225,229,217,233,211,103,110,143,171,157,184,146,102,86,46,16,6,236,250,241,230,238,250,255,253,6,13,5,252,244,229,161,106,53,29,15,246,204,175,173,144,146,247,250,9,2,243,24,30,31,40,52,21,4,246,242,237,237,238,237,191,158,202,239,232,3,14,19,28,38,52,98,152,244,249,43,63,94,100,93,39,21,6,253,2,23,36,50,207,225,213,233,245,226,221,233,14,3,236,198,228,1,251,228,226,220,205,222,228,226,188,129,89,185,167,171,213,211,218,245,36,137,131,85,48,11,241,250,11,14,34,63,87,133,177,193,217,235,8,54,101,90,76,62,57,67,59,41,13,1,6,23,29,41,80,134,168,223,11,31,31,41,38,27,23,16,236,201,194,222,233,237,0,10,4,249,251,14,40,31,7,236,167,136,172,171,15,28,40,44,36,14,5,14,253,234,234,242,244,239,230,0,34,74,88,69,69,16,245,253,248,222,55,61,91,4,5,252,239,217,213,200,150,131,139,145,160,185,209,220,228,210,202,170,131,108,11,246,242,14,27,26,25,28,34,45,60,64,90,151,246,229,236,255,247,253,2,243,235,229,244,10,177,173,195,198,185,202,187,135,103,81,31,246,232,245,241,250,246,251,10,17,16,6,15,21,10,10,19,18,23,29,59,46,31,19,14,20,24,22,10,24,62,57,58,45,28,27,42,58,48,43,53,38,47,38,10,
2019-08-10 08:57:14 +00:00
EX namespace windmap {
#if HDR
static const int NOWINDBELOW = 8;
static const int NOWINDFROM = 120;
#endif
map<int, int> getid;
vector<vector<int> > neighbors;
vector<cellwalker> samples;
2019-08-10 08:57:14 +00:00
EX int getId(cell *c) {
auto i = fieldpattern::fieldval_uniq(c);
return getid[i]-1;
}
int getId(cellwalker cw) {
auto i = fieldpattern::fieldval_uniq(cw.at);
auto &id = getid[i];
if(id == 0) { samples.push_back(cw); id = isize(samples); }
return id-1;
}
EX vector<unsigned char> windcodes;
void wcheck(cell *a, cell *b) {
int i = getId(a);
if(b) {
int j = getId(b);
for(auto nn: neighbors[i]) if(nn == j) return;
printf("neigbor not found!\n");
}
else {
printf("%d", windcodes[i]);
for(auto nn: neighbors[i]) printf("n%d ", windcodes[nn]);
printf("\n");
}
}
2019-08-10 08:57:14 +00:00
EX void create() {
samples.clear();
neighbors.clear();
getid.clear();
getId(cellwalker(currentmap->gamestart(), 0));
2018-06-22 12:47:24 +00:00
for(int k=0; k<isize(samples); k++) {
cellwalker cw = samples[k];
// cw.spin = 0;
neighbors.emplace_back();
auto &v = neighbors.back();
2020-07-12 18:52:32 +00:00
if(NONSTDVAR && !sphere && !arcm::in() && !hybri && !INVERSE)
for(int l=0; l<S7; l++) {
v.push_back(getId(cw + cth + l + wstep + cth));
}
2018-08-28 17:00:09 +00:00
else
for(int l=0; l<cw.at->type; l++) v.push_back(getId(cw+l+wstep));
}
2018-06-22 12:47:24 +00:00
int N = isize(samples);
int* precomp = NULL;
windcodes.resize(N);
if(N == 18920) precomp = windcodes18920;
if(N == 5676) precomp = windcodes5676;
if(precomp && (hyperbolic || hybri) && isize(currfp.matrices)) {
2018-06-22 12:47:24 +00:00
int randval = hrand(isize(currfp.matrices));
for(int i=0; i<N; i++)
windcodes[i] = precomp[getid[fieldpattern::fieldval_uniq_rand(samples[i].at, randval)]-1];
return;
}
int tries = 0;
int maxtries = specialland == laVolcano || specialland == laBlizzard || chaosmode ? 20 : 1;
tryagain:
for(int i=0; i<N; i++) windcodes[i] = hrand(256);
vector<bool> inqueue(N, true);
vector<int> tocheck;
for(int i=0; i<N; i++) tocheck.push_back(i);
2018-06-22 12:47:24 +00:00
hrandom_shuffle(&tocheck[0], isize(tocheck));
2018-06-22 12:47:24 +00:00
for(int a=0; a<isize(tocheck); a++) {
if(a >= 200*N) { printf("does not converge\n"); break; }
int bestval = 1000000000, best = 0;
int i = tocheck[a];
for(int k=0; k<256; k++) {
int j = windcodes[i] + k;
int cval = 0;
for(int c2: neighbors[i]) {
int d = (j - windcodes[c2]) & 255;
if(d > 128) d = 256 - d;
cval += d*d;
}
if(cval < bestval) bestval = cval, best = j;
}
if(windcodes[i] != best)
for(int c2: neighbors[i])
if(!inqueue[c2])
inqueue[c2] = true, tocheck.push_back(c2);
inqueue[i] = false;
windcodes[i] = best;
}
int ingroup[4];
for(int u=0; u<4; u++) ingroup[u] = 0;
for(int i=0; i<N; i++) ingroup[windcodes[i] >> 6]++;
for(int u=0; u<4; u++) if(!ingroup[u]) {
tries++;
if(tries < maxtries) goto tryagain;
}
if(tries >= maxtries && maxtries >= 20) {
addMessage("Failed to generate an interesting wind/lava pattern.");
}
else if(false) {
2018-06-22 12:47:24 +00:00
printf("tocheck size = %d\n", isize(tocheck));
printf("if(N == %d) {\n", N);
printf(" windcodes = {");
for(int i=0; i<N; i++) printf("%d,", windcodes[i]);
printf("};\n");
printf(" return;\n");
printf(" }\n");
}
}
2017-09-30 09:46:41 +00:00
2019-08-10 08:57:14 +00:00
EX int at(cell *c) {
2017-09-30 09:46:41 +00:00
return windmap::windcodes[windmap::getId(c)];
}
EX }
2019-02-17 17:33:15 +00:00
#endif
2017-10-08 10:10:40 +00:00
// Halloween namespace
2019-08-09 23:31:44 +00:00
EX namespace halloween {
EX cell *dragoncells[4];
2017-10-08 10:10:40 +00:00
vector<cell*> srch;
cell *farempty(bool lastresort = false) {
int maxdist = 0;
vector<cell*> validcells;
int firstfar1 = 0;
2018-06-22 12:47:24 +00:00
for(int i=1; i<isize(dcal); i++) {
2017-10-08 10:10:40 +00:00
bool okay =
dcal[i]->item == itNone && dcal[i]->monst == moNone && dcal[i]->wall == waNone;
if(lastresort)
okay = dcal[i]->wall != waChasm && !isMultitile(dcal[i]->monst);
if(okay) {
if(dcal[i]->cpdist > maxdist)
2018-06-22 12:47:24 +00:00
firstfar1 = isize(validcells),
2017-10-08 10:10:40 +00:00
maxdist = dcal[i]->cpdist;
validcells.push_back(dcal[i]);
}
}
2018-06-22 12:47:24 +00:00
int qvc = isize(validcells);
2017-10-08 10:10:40 +00:00
if(qvc == firstfar1) return farempty(true);
return validcells[firstfar1 + hrand(qvc - firstfar1)];
}
int demoncount;
int swordpower;
int dragoncount;
void reset() {
demoncount = 0;
swordpower = 0;
dragoncount = 0;
}
eMonster nextDemon() {
demoncount++;
if(demoncount % 9 == 0) return moGreater;
return moLesser;
}
void putMonster(eMonster m) {
cell *c = farempty();
2020-02-29 16:58:59 +00:00
changes.ccell(c);
2017-10-08 10:10:40 +00:00
c->hitpoints = 3;
c->monst = m;
playSeenSound(c);
if(!kills[m]) switch(m) {
case moGhost:
addMessage(XLAT("Ghosts can move through chasms!"));
break;
case moSkeleton:
addMessage(XLAT("Push Skeletons into the holes!"));
break;
case moDraugr:
addMessage(XLAT("You'll need your magical sword against the Draugar!"));
break;
case moLesser:
addMessage(XLAT("Demons are slow, but you'll need the experience against stronger ones..."));
break;
case moGreater:
addMessage(XLAT("You will need more experience to defeat the Greater Demon!"));
break;
case moPyroCultist:
addMessage(XLAT("Cultists throw fire at you from distance!"));
break;
case moFlailer:
addMessage(XLAT("Defeat Flail Guards by moving away from them."));
break;
case moVampire:
addMessage(XLAT("Vampire Bats drain your magical powers!"));
break;
default: break;
}
c->stuntime = 2;
}
2019-08-09 23:31:44 +00:00
EX void getTreat(cell *where) {
2017-10-08 10:10:40 +00:00
if(!items[itTreat]) reset();
gainItem(itTreat);
2020-09-17 02:35:49 +00:00
changes.at_commit(after_treat);
}
EX void after_treat() {
2017-10-08 10:10:40 +00:00
farempty()->item = itTreat;
int itr = items[itTreat];
items[itOrbTime] += 30;
2020-02-29 16:58:59 +00:00
changes.value_keep(dragoncount);
changes.value_keep(demoncount);
changes.value_keep(swordpower);
2017-10-08 10:10:40 +00:00
int monpower = 19 + itr + hrand(itr);
int mcount = 0;
while(monpower > 0) {
int id = hrand(10000);
#define CHANCE(x) (id -= x, id < 0)
if(CHANCE(400) && itr >= 5) {
putMonster(moGhost);
monpower -= 30;
mcount++;
}
else if(CHANCE(400)) {
putMonster(pick(moWitch, moZombie));
monpower -= 20;
mcount++;
}
else if(CHANCE(400) && itr >= 5) {
putMonster(nextDemon());
monpower -= 10;
mcount++;
}
else if(CHANCE(100) && itr >= 12) {
putMonster(moVampire);
monpower -= 10;
swordpower -= 5;
if(swordpower < 0) swordpower = 0;
mcount++;
}
else if(CHANCE(400) && swordpower > 0 && !mcount && itr >= 15) {
putMonster(moDraugr);
swordpower -= 3;
monpower -= 100;
mcount++;
}
else if(CHANCE(400) && itr >= 10 && !mcount && itr >= 10) {
putMonster(moSkeleton);
monpower -= 60;
mcount++;
}
else if(CHANCE(10) && itr >= 15) {
putMonster(moWitchFire);
monpower -= 35;
mcount++;
}
else if(CHANCE(100) && itr >= 5) {
putMonster(moFlailer);
monpower -= 35;
mcount++;
}
else if(CHANCE(100) && itr >= 5) {
putMonster(moPyroCultist);
monpower -= 35;
mcount++;
}
else if(CHANCE(5) && itr >= 20 && kills[moSkeleton]) {
putMonster(moFatGuard);
monpower -= 35;
mcount++;
}
else if(CHANCE(5) && itr >= 30 && kills[moFlailer]) {
putMonster(moHedge);
monpower -= 35;
mcount++;
}
else if(CHANCE(5) && itr >= 40 && kills[moHedge]) {
putMonster(moLancer);
monpower -= 60;
mcount++;
}
else if(CHANCE(1) && itr >= 50 && kills[moHedge]) {
putMonster(moFireFairy);
monpower -= 40;
mcount++;
}
else if(CHANCE(5) && itr >= 60) {
putMonster(moBomberbird);
monpower -= 25;
mcount++;
}
else if(CHANCE(5) && itr >= 60) {
putMonster(moRatlingAvenger);
monpower -= 30;
mcount++;
}
else if(CHANCE(5) && itr >= 60) {
putMonster(moVineBeast);
monpower -= 30;
mcount++;
}
else if(CHANCE(5) && itr >= 60) {
dragoncount++;
}
2020-09-17 02:36:36 +00:00
else if(dragoncount && BITRUNCATED && geometry == gSphere && !mcount) {
2017-10-08 10:10:40 +00:00
bool fill = false;
for(int i=0; i<4; i++)
if(!dragoncells[i] || dragoncells[i]->monst)
fill = true;
swap(dragoncells[0], dragoncells[3]);
swap(dragoncells[1], dragoncells[2]);
if(fill) continue;
for(int i=0; i<4; i++) {
dragoncells[i]->monst = i ? moDragonTail : moDragonHead;
dragoncells[i]->mondir = i==3 ? NODIR : neighborId(dragoncells[i], dragoncells[i+1]);
dragoncells[i]->hitpoints = 1;
dragoncells[i]->stuntime = 1;
playSeenSound(dragoncells[i]);
}
monpower -= 200;
mcount++;
dragoncount--;
}
}
int id = hrand(100);
if(items[itTreat] == 1) {
#if !ISMOBILE
2017-10-08 10:10:40 +00:00
addMessage(XLAT("Hint: use arrow keys to scroll."));
#endif
}
else if(items[itTreat] == 2) {
#if !ISMOBILE
2017-10-08 10:10:40 +00:00
addMessage(XLAT("Hint: press 1 2 3 4 to change the projection."));
#endif
}
else if(items[itTreat] == 3) {
items[itOrbShell] += 20;
addMessage(XLAT("You gain a protective Shell!"));
}
else if(items[itTreat] == 4) {
items[itOrbShell] += 10;
addMessage(XLAT("Hint: magical powers from Treats expire after a number of uses."));
}
else if(kills[moSkeleton] && CHANCE(10)) {
addMessage(XLAT("A magical energy sword. Don't waste its power!"));
items[itOrbSword] += 5; // todo facing
swordpower += 5;
}
else if(kills[moDraugr] && items[itOrbSword] && CHANCE(10)) {
addMessage(XLAT("Another energy sword!"));
items[itOrbSword2] += 5;
swordpower += 5;
}
else if(kills[moFlailer] && CHANCE(10)) {
items[itOrbThorns] += 5;
addMessage(XLAT("You got Thorns! Stab monsters by moving around them."));
}
else if(kills[moGhost] && CHANCE(10)) {
items[itOrbAether] += 5;
addMessage(XLAT("Aethereal powers let you go through the holes."));
}
else {
if(items[itOrbShell] > ORBBASE)
addMessage(XLAT("The tasty treat increases your protection."));
else
addMessage(XLAT("You gain your protective Shell back!"));
items[itOrbShell] += 5;
}
}
2019-08-09 23:31:44 +00:00
EX }
2017-10-08 10:10:40 +00:00
// ... also includes the Ivory Tower
EX namespace dungeon {
2017-10-08 10:10:40 +00:00
void towerError(cell *c) {
// only care in the standard geometry -- weird ones are intentionally left buggy
if(!weirdhyperbolic && !sphere && !quotient)
raiseBuggyGeneration(c, "ivory tower/dungeon generation error");
}
2017-10-08 10:10:40 +00:00
void buildIvoryTower(cell *c) {
/* if(int(c->landparam) % 5 == 0)
c->wall = waCamelot;
*/
if(euclid) {
2019-11-27 00:01:20 +00:00
if(quotient) return;
auto co = euc2_coordinates(c);
int x = co.first, y = co.second;
string tab6[] = {
2017-10-08 10:10:40 +00:00
".####...",
"L...L...",
".L..L...",
"L...L...",
"........",
"........"
};
string tab4[] = {
"L####L..",
"L....L..",
"L....L..",
"L....L..",
"........",
"........"
};
2017-10-08 10:10:40 +00:00
int y0 = y; if(y>32768) y0 -= 65536;
y0 += 5; y0 %= 12; if(y0<0) y0+=12;
if(y0 >= 6) { y0 -= 6; x += 4; }
char ch = euc::in(2,6) ? tab6[y0][(x+(y+1)/2)&7] : tab4[y0][x&7];
2017-10-08 10:10:40 +00:00
if(ch == '#')
c->wall = waPlatform;
else if(ch == 'L')
c->wall = waLadder;
}
2019-05-08 16:33:08 +00:00
else if(WDIM == 3) {
2019-03-23 15:28:48 +00:00
int cnt = 0;
int below = 0;
manual_celllister cl;
cl.add(c);
for(int i=0; i<isize(cl.lst); i++) {
cell *c1 = cl.lst[i];
generate_around(c1);
if(coastvalEdge(c1) == coastvalEdge(c) - 3) {
if(c1->landflags == 3) cnt++;
continue;
}
if(c1->landflags == 3) below++;
forCellEx(c2, c1) if(coastvalEdge(c2) < coastvalEdge(c1))
cl.add(c2);
}
if(cnt) c->wall = waPlatform;
else if(below && coastvalEdge(c) < 3) c->wall = waPlatform;
}
2017-10-08 10:10:40 +00:00
else if(true) {
cell *c2 = c;
cell *c3 = c;
bool rdepths[5];
for(int i=0; i<5; i++) {
if(coastvalEdge(c2) == 0) {
rdepths[i] = false;
}
else {
cell *c4 = c2;
if(c2 != c3 && !isNeighbor(c2, c3)) {
for(int i=0; i<c2->type; i++) if(c2->move(i) && isNeighbor(c2->move(i), c3))
c4 = c2->move(i);
2017-10-08 10:10:40 +00:00
}
rdepths[i] = c2 && c3 && c4 && (c2->landflags == 3 || c3->landflags == 3 || c4->landflags == 3);
if(c2) generate_around(c2);
if(c3) generate_around(c3);
c2 = ts::left_parent(c2, coastvalEdge);
c3 = ts::right_parent(c3, coastvalEdge);
if(!c2) { towerError(c); return; }
if(!c3) { towerError(c); return; }
2017-10-08 10:10:40 +00:00
}
}
if(rdepths[3]) {
c->wall = waPlatform;
// if(!c4->item) c4->item = itPalace;
}
else if(!rdepths[2] && !rdepths[4] && !rdepths[1]) {
c2 = c;
c3 = c;
generate_around(c);
cell *c4 = ts::left_of(c, coastvalEdge);
cell *c5 = ts::right_of(c, coastvalEdge);
2017-10-08 10:10:40 +00:00
for(int i=0; i<3; i++) {
if(coastvalEdge(c2) == 0) break;
for(cell *cx: {c2, c3, c4, c5}) if(cx) generate_around(cx);
if(c2 && c4 && c4->landflags == 3 && c2->landflags != 3 && c4 == ts::left_of(c2, coastvalEdge))
2017-10-08 10:10:40 +00:00
c->wall = waLadder;
if(c3 && c5 && c5->landflags == 3 && c3->landflags != 3 && c5 == ts::right_of(c3, coastvalEdge))
2017-10-08 10:10:40 +00:00
c->wall = waLadder;
buildEquidistant(c4); buildEquidistant(c5);
if(c2) c2 = ts::left_parent(c2, coastvalEdge);
if(c3) c3 = ts::right_parent(c3, coastvalEdge);
if(c4) c4 = ts::left_parent(c4, coastvalEdge);
if(c5) c5 = ts::right_parent(c5, coastvalEdge);
2017-10-08 10:10:40 +00:00
}
}
}
else c->wall = waCIsland;
}
int dungeonFlags(cell *c) {
if(!c) return 0;
buildEquidistant(c);
bool rdepths[5];
int switchcount = 0;
2019-03-23 15:28:48 +00:00
2019-05-08 16:33:08 +00:00
if(WDIM == 3) {
2019-03-23 15:28:48 +00:00
for(int i=0; i<5; i++) rdepths[i] = false;
manual_celllister cl;
cl.add(c);
int d = coastvalEdge(c);
for(int i=0; i<isize(cl.lst); i++) {
cell *c1 = cl.lst[i];
generate_around(c1);
int d1 = d - coastvalEdge(c);
if(c1->landflags == 3) rdepths[d1] = true;
if(c1->landflags == 1) switchcount++;
if(d1 == 4) break;
forCellEx(c2, c1) if(coastvalEdge(c2) < coastvalEdge(c1))
cl.add(c2);
2017-10-08 10:10:40 +00:00
}
2019-03-23 15:28:48 +00:00
}
else {
cell *c2 = c;
cell *c3 = c;
for(int i=0; i<5; i++) {
if(coastvalEdge(c2) == 0) {
rdepths[i] = false;
}
else {
cell *c4 = c2;
if(c2 != c3 && !isNeighbor(c2, c3)) {
for(int i=0; i<c2->type; i++) if(c2->move(i) && isNeighbor(c2->move(i), c3))
c4 = c2->move(i);
}
rdepths[i] = c2 && c3 && c4 && (c2->landflags == 3 || c3->landflags == 3 || c4->landflags == 3);
if((c2&&c2->landflags == 1) || (c3&&c3->landflags == 1) || (c4&&c4->landflags == 1))
switchcount++;
generate_around(c2);
generate_around(c3);
c2 = ts::left_parent(c2, coastvalEdge);
c3 = ts::right_parent(c3, coastvalEdge);
if(!c2) { towerError(c); return 0; }
if(!c3) { towerError(c); return 0; }
2017-10-08 10:10:40 +00:00
}
}
}
int res = 0;
if(rdepths[3]) res |= 1;
if(rdepths[2]) res |= 2;
if(switchcount&1) res |= 4;
return res;
}
void placeGate(cell *c, eWall w) {
if(w == waOpenGate) {
c->wall = waClosedGate;
toggleGates(c, waOpenPlate, 0);
}
if(w == waClosedGate) {
c->wall = waOpenGate;
toggleGates(c, waClosePlate, 0);
}
}
bool isGate(eWall w) {
return w == waOpenGate || w == waClosedGate;
}
void placeRandomGate(cell *c) {
placeGate(c, hrand(2) ? waOpenGate : waClosedGate);
}
2019-03-23 15:28:48 +00:00
cell *random_child(cell *c, const cellfunction& cf) {
generate_around(c);
vector<cell*> children;
forCellEx(c2, c) if(cf(c2) > cf(c)) children.push_back(c2);
if(!isize(children)) return NULL;
return children[hrand(isize(children))];
}
2017-10-08 10:10:40 +00:00
void build(cell *c) {
/* if(int(c->landparam) % 5 == 0)
c->wall = waCamelot;
*/
if(true) {
if(coastvalEdge(c) == 1) forCellEx(c2, c)
if(c2->land != laBarrier && c2->land != laDungeon) {
c->wall = waLadder;
c->wparam = 3;
}
int df = dungeonFlags(c);
if(df&1) {
generate_around(c);
2019-05-08 16:33:08 +00:00
int df1 = WDIM == 3 ? 0 : dungeonFlags(ts::left_of(c, coastvalEdge));
int df2 = WDIM == 3 ? 0 : dungeonFlags(ts::right_of(c, coastvalEdge));
2017-10-08 10:10:40 +00:00
c->wparam = 0;
if(hrand(100) < (c->landparam % 5 == 0 ? 80 : 20)) {
if(!(df1&1)) c->wparam = 1;
if(!(df2&1)) c->wparam = 2;
}
if(df&4)
placeRandomGate(c);
else if(c->wparam == 0 && c->landparam % 5 == 0 && hrand(100) < 10) {
c->wall = waLadder;
c->wparam = 3 + hrand(2);
}
else
c->wall = waPlatform;
}
if(c->wparam) {
cell *c2 =
2019-05-08 16:33:08 +00:00
WDIM == 3 ? random_child(c, coastvalEdge) :
c->wparam == 1 ? ts::add(c, 1, 2, coastvalEdge) :
c->wparam == 2 ? ts::add(c, -1, -2, coastvalEdge) :
c->wparam == 3 ? ts::add(c, 1, 3, coastvalEdge) :
c->wparam == 4 ? ts::add(c, -1, -3, coastvalEdge) :
2017-10-08 10:10:40 +00:00
NULL;
if(c2) {
c2->wall = c->wall, c2->wparam = c->wparam;
if(c2->wall == waPlatform && hrand(10) < 2)
placeRandomGate(c2);
if(isGate(c2->wall) && hrand(10) < 2)
c2->wall = waPlatform;
}
}
}
else c->wall = waCIsland;
}
void buildPlates(cell *c) {
if(c->wall) return;
int neargate = 0;
int neargateDown = 0;
int neargateEq = 0;
int qup = 0;
forCellEx(c2, c) {
int d = coastvalEdge(c2) - coastvalEdge(c);
if(isGate(c2->wall)) {
neargate++;
if(d>0) neargateDown++;
if(d==0) neargateEq = 0;
}
if(d<0) qup++;
}
if(!neargate) return;
int hr = 99;
if(neargate == neargateDown && qup == 1)
hr = hrand(12);
else if((zebra40(c) >= 40) && !(neargateEq && neargateDown))
hr = hrand(36);
else if(zebra40(c) >= 40)
hr = hrand(5000);
if(hr < 5) c->wall = waClosePlate;
else if(hr < 10) c->wall = waOpenPlate;
}
bool is02(int i) { return i == 0 || i == 2; }
EX void all(cell *c, int d) {
2017-10-08 10:10:40 +00:00
if(d == 8 && (c->land == laIvoryTower || c->land == laDungeon) && !euclid) {
2019-05-08 16:33:08 +00:00
if(hrand(1000) < 75 && (WDIM == 3 || (c->landparam & 1))) {
2017-10-08 10:10:40 +00:00
c->landflags = 3;
}
else c->landflags = 0;
}
if(d == 8 && c->land == laDungeon && !euclid) {
if(hrand(1000) < 240 && is02(c->landparam%5) ) {
c->landflags = 3;
}
else if(hrand(1000) < 90)
c->landflags = 1;
else c->landflags = 0;
}
if(d == 7 && c->land == laIvoryTower) buildIvoryTower(c);
2019-03-23 15:28:48 +00:00
if(d == (BARLEV == 8 ? 7 : 8) && c->land == laDungeon) build(c);
2017-10-08 10:10:40 +00:00
if(d == 7 && c->land == laDungeon) buildPlates(c);
}
EX }
}