diff --git a/Makefile.am b/Makefile.am index dcec9d73..be5c758e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,7 +26,7 @@ language-data.cpp: langen ./langen > language-data.cpp autohdr.h: makeh language-data.cpp *.cpp - ./makeh classes.cpp locations.cpp hyperpoint.cpp geometry.cpp goldberg.cpp init.cpp floorshapes.cpp cell.cpp multi.cpp shmup.cpp pattern2.cpp mapeditor.cpp graph.cpp textures.cpp hprint.cpp language.cpp *.cpp > autohdr.h + ./makeh classes.cpp locations.cpp hyperpoint.cpp geometry.cpp goldberg.cpp init.cpp floorshapes.cpp cell.cpp multi.cpp shmup.cpp pattern2.cpp mapeditor.cpp graph.cpp textures.cpp hprint.cpp language.cpp util.cpp complex.cpp *.cpp > autohdr.h ############################# # Platform specific setup diff --git a/Makefile.simple b/Makefile.simple index ac9b6934..ee7cf022 100644 --- a/Makefile.simple +++ b/Makefile.simple @@ -159,7 +159,7 @@ makeh$(EXE_EXTENSION): makeh.cpp $(CXX) makeh.cpp -o $@ autohdr.h: makeh$(EXE_EXTENSION) *.cpp - ./makeh classes.cpp locations.cpp hyperpoint.cpp geometry.cpp goldberg.cpp init.cpp floorshapes.cpp cell.cpp multi.cpp shmup.cpp pattern2.cpp mapeditor.cpp graph.cpp textures.cpp hprint.cpp language.cpp complex.cpp *.cpp > autohdr.h + ./makeh classes.cpp locations.cpp hyperpoint.cpp geometry.cpp goldberg.cpp init.cpp floorshapes.cpp cell.cpp multi.cpp shmup.cpp pattern2.cpp mapeditor.cpp graph.cpp textures.cpp hprint.cpp language.cpp util.cpp complex.cpp *.cpp > autohdr.h language-data.cpp: langen$(EXE_EXTENSION) ./langen > language-data.cpp diff --git a/attack.cpp b/attack.cpp index f1760002..fd87babf 100644 --- a/attack.cpp +++ b/attack.cpp @@ -216,7 +216,7 @@ EX void killIvy(cell *c, eMonster who) { c->monst = moIvyDead; // NEWYEARFIX for(int i=0; itype; i++) if(c->move(i)) if(isIvy(c->move(i)) && c->move(i)->mondir == c->c.spin(i)) - killIvy(c->move(i), who); + killIvy(c->move(i), who), kills[moIvyDead]++; } EX void prespill(cell* c, eWall t, int rad, cell *from) { @@ -293,14 +293,14 @@ EX void prespill(cell* c, eWall t, int rad, cell *from) { // block spill if(t == waTemporary) return; // cwt.at->item = itNone; - if(rad) for(int i=0; itype; i++) if(c->move(i)) - prespill(c->move(i), t, rad-1, c); + if(rad) for(cell *c2: adj_minefield_cells(c)) + prespill(c2, t, rad-1, c); } EX void spillfix(cell* c, eWall t, int rad) { if(c->wall == waTemporary) c->wall = t; - if(rad) for(int i=0; itype; i++) if(c->move(i)) - spillfix(c->move(i), t, rad-1); + if(rad) for(cell *c2: adj_minefield_cells(c)) + spillfix(c2, t, rad-1); } EX void spill(cell* c, eWall t, int rad) { @@ -391,7 +391,14 @@ EX void killMutantIvy(cell *c, eMonster who) { removeIvy(c); for(int i=0; itype; i++) if(c->move(i)->mondir == c->c.spin(i) && (isMutantIvy(c->move(i)) || c->move(i)->monst == moFriendlyIvy)) - killMutantIvy(c->move(i), who); + kills[c->move(i)->monst]++, killMutantIvy(c->move(i), who); + if(c->land == laClearing) clearing::imput(c); + } + +EX bignum ivy_total() { + return kills[moMutant] + kills[moFriendlyIvy] + + kills[moIvyRoot] + kills[moIvyHead] + kills[moIvyBranch] + kills[moIvyWait] + kills[moIvyDead] + + clearing::imputed; } EX void killMonster(cell *c, eMonster who, flagtype deathflags IS(0)) { @@ -441,7 +448,7 @@ EX void killMonster(cell *c, eMonster who, flagtype deathflags IS(0)) { if(m == moHunterGuard) m = moHunterDog; if(m == moHunterChanging) m = moHunterDog; if(m == moWolfMoved) m = moWolf; - if(!isBulletType(m)) kills[m]++; + if(!isBulletType(m) && m != moIvyDead) kills[m]++; if(saved_tortoise_on(c)) c->item = itNone; @@ -462,7 +469,12 @@ EX void killMonster(cell *c, eMonster who, flagtype deathflags IS(0)) { if(isMutantIvy(m) || m == moFriendlyIvy) { pcount = 0; + if(isMutantIvy(m)) clearing::direct++; + bignum s = ivy_total() - 1; killMutantIvy(c, who); + s = ivy_total() - s; + if(vid.bubbles_special && s > bignum(1)) + drawBubble(c, 0xFFFF00, s.get_str(100), .5); } if(m == moPrincess) { @@ -680,12 +692,27 @@ EX void killMonster(cell *c, eMonster who, flagtype deathflags IS(0)) { } if(m == moLesser && !(kills[m] % 10)) degradeDemons(); + if(m == moLesser) { + if(kills[m] % 10) { + if(vid.bubbles_special) + drawBubble(c, 0xFF0000, its(kills[m]%10), 1); + } + else { + if(vid.bubbles_special) + drawBubble(c, 0xFF8000, "+1 XP", .8); + degradeDemons(); + } + } if(isIvy(c)) { pcount = 0; eMonster m = c->monst; + bignum s = ivy_total() - 1; /*if((m == moIvyBranch || m == moIvyHead) && c->move(c->mondir)->monst == moIvyRoot) ivynext(c, moIvyNext); */ killIvy(c, who); + s = ivy_total() - s; + if(s > bignum(1) && vid.bubbles_special) + drawBubble(c, 0xFFFF00, s.get_str(100), .5); if(m == moIvyBranch || m == moIvyHead || m == moIvyNext) { int qty = 0; cell *c2 = c->move(c->mondir); diff --git a/bigstuff.cpp b/bigstuff.cpp index df9e49b1..544a2f5a 100644 --- a/bigstuff.cpp +++ b/bigstuff.cpp @@ -1782,7 +1782,7 @@ EX void moreBigStuff(cell *c) { c->land = laClearing, c->wall = waNone; // , c->monst = moNone, c->item = itNone; } else if(d == 1 && !tactic::on && !eubinary) - c->wall = waSmallTree, c->monst = moNone, c->item = itNone; + c->wall = waSmallTree, c->monst = moNone, c->item = itNone, c->landparam = 1; } } diff --git a/celldrawer.cpp b/celldrawer.cpp index ca38ce8e..b98e5de1 100644 --- a/celldrawer.cpp +++ b/celldrawer.cpp @@ -2242,9 +2242,20 @@ void celldrawer::add_map_effects() { int tim = ticks - lightat; if(tim > 1000) tim = 800; if(elec::havecharge && tim > 400) tim = 400; - for(int t=0; ttype; t++) if(c->move(t) && c->move(t)->ligon) { - int lcol = darkena(gradient(iinf[itOrbLightning].color, 0, 0, tim, 1100), 0, 0xFF); - queueline(V*chei(xspinpush(ticks * M_PI / cgi.S42, cgi.hexf/2), rand() % 1000, 1000) * C0, V*chei(currentmap->adj(c, t), rand() % 1000, 1000) * C0, lcol, 2 + vid.linequality); + for(int t=0; ttype; t++) if(c->move(t)) { + if(c->move(t)->ligon) { + int lcol = darkena(gradient(iinf[itOrbLightning].color, 0, 0, tim, 1100), 0, 0xFF); + queueline(V*chei(xspinpush(ticks * M_PI / cgi.S42, cgi.hexf/2), rand() % 1000, 1000) * C0, V*chei(currentmap->adj(c, t), rand() % 1000, 1000) * C0, lcol, 2 + vid.linequality); + } + for(int u: {-1, 1}) { + cellwalker cw = cellwalker(c, t) + wstep + u; + if(u == -1 && VALENCE == 4) continue; + cell *c2 = cw.peek(); + if(c2 && c2->ligon) { + int lcol = darkena(gradient(iinf[itOrbLightning].color, 0, 0, tim, 1100), 0, 0xFF); + queueline(V*chei(xspinpush(ticks * M_PI / cgi.S42, cgi.hexf/2), rand() % 1000, 1000) * C0, V*chei(currentmap->adj(c, t)*currentmap->adj(cw.at, cw.spin), rand() % 1000, 1000) * C0, lcol, 2 + vid.linequality); + } + } } } diff --git a/classes.cpp b/classes.cpp index 911a1210..96cf67a3 100644 --- a/classes.cpp +++ b/classes.cpp @@ -722,7 +722,7 @@ enum eGeometry { gBinary4, gSol, gKiteDart2, gKiteDart3, gNil, gProduct, gRotSpace, gTernary, gNIH, gSolN, gInfOrder, gSpace336, gSpace344, gCrystal344, - gArnoldCat, gArbitrary, + gArnoldCat, gArbitrary, gInfOrder4, gGUARD}; enum eGeometryClass { gcHyperbolic, gcEuclid, gcSphere, gcSolNIH, gcNil, gcProduct, gcSL2 }; @@ -887,6 +887,7 @@ EX vector ginf = { {"{3,4,4}","Crystal", "4D crystal in H3", "Cryst3" , 8, 4, qIDEAL | qANYQ | qCRYSTAL, giHyperb3, 0x52000, {{7, 3}}, eVariation::pure}, {"cat", "cat", "Arnold's cat mapping torus", "cat", 12, 3, qBINARY | qSOL | qsBQ | qOPTQ, giSolNIH, 0x52200, {{6, 4}}, eVariation::pure}, {"arb", "arb", "arbitrary", "arb", 7, 3, qEXPERIMENTAL, giEuclid2, 0, {{7, 5}}, eVariation::pure}, + {"{4,oo}", "none", "{4,∞} (infinite squares)", "oox4", 4, 100, qIDEAL, giHyperb2, 0x49400, {{5, 5}}, eVariation::pure}, }; // bits: 9, 10, 15, 16, (reserved for later) 17, 18 diff --git a/complex.cpp b/complex.cpp index 95b98505..902b1019 100644 --- a/complex.cpp +++ b/complex.cpp @@ -306,9 +306,7 @@ EX namespace elec { if(from != 1) charges[id].lowlink = 1; } - for(int i=0; itype; i++) { - cell *c2 = c->move(i); - if(!c2) continue; + for(cell *c2: adj_minefield_cells(c)) { if(c2->listindex == from) continue; eCharge ct = getCharge(c2); if(conduct(chh, ct)) @@ -956,6 +954,46 @@ EX namespace clearing { steps--; ds++; } } + + typedef tuple celltype; + + map > stats; + + EX bignum imputed; + EX int direct; + + map > 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) { + if(bounded) return; + if(score.count(c)) return; + auto& is = score[c]; + celltype t = get_celltype(c); + 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; + } + } + EX } EX namespace whirlpool { @@ -2047,14 +2085,16 @@ EX namespace heat { if(isFire(c)) hmod += 4 * xrate; if(isPrincess(c->monst)) hmod += (markEmpathy(itOrbWinter) ? -1.2 : 1.2) * xrate; - forCellEx(ct, c) { + auto ls = adj_minefield_cells(c); + + for(cell* ct: ls) { if(!isIcyLand(ct) && isFire(ct)) hmod += xrate*.1; if(ct->land == laVolcano) hmod += xrate * (ct->wall == waMagma ? .4 : .2); } - forCellEx(ct, c) { + for(cell* ct: ls) { if(!isIcyLand(ct)) { // make sure that we can still enter Cocytus, // it won't heat up right away even without Orb of Winter or Orb of Speed @@ -2139,7 +2179,8 @@ EX namespace heat { cell *last = c->move(c->type-1); - forCellEx(c2, c) { + auto ls = adj_minefield_cells(c); + for(cell* c2: ls) { if(c->wall == waPartialFire) { // two partial fires adjacent are necessary to spread @@ -2260,7 +2301,7 @@ EX void livecaves() { hv = 0; if(c->monst == moDarkTroll) c->monst = moTroll; if(c->item || c->monst || c->cpdist == 0) continue; - forCellEx(c2, c) { + 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)) diff --git a/config.cpp b/config.cpp index e3f66edc..7980619b 100644 --- a/config.cpp +++ b/config.cpp @@ -618,6 +618,10 @@ EX void initConfig() { addsaver(s2xe::qrings, "s2xe-rings"); addsaver(rots::underlying_scale, "rots-underlying-scale"); + + addsaver(vid.bubbles_special, "bubbles-special", 1); + addsaver(vid.bubbles_threshold, "bubbles-special", 1); + addsaver(vid.bubbles_all, "bubbles-special", 0); #if CAP_SHMUP multi::initConfig(); @@ -996,6 +1000,22 @@ EX void menuitem_sightrange(char c IS('c')) { dialog::add_action(edit_sightrange); } +EX void showSpecialEffects() { + cmode = vid.xres > vid.yres * 1.4 ? sm::SIDE : sm::MAYDARK; + gamescreen(0); + dialog::init(XLAT("extra graphical effects")); + + dialog::addBoolItem_action(XLAT("particles on attack"), (vid.particles), 'p'); + dialog::addBoolItem_action(XLAT("floating bubbles: special"), vid.bubbles_special, 's'); + dialog::addBoolItem_action(XLAT("floating bubbles: treasure thresholds"), vid.bubbles_threshold, 't'); + dialog::addBoolItem_action(XLAT("floating bubbles: all treasures"), vid.bubbles_all, 'a'); + dialog::addBoolItem_action(XLAT("background particle effects"), (vid.backeffects), 'b'); + + dialog::addBreak(50); + dialog::addBack(); + dialog::display(); + } + EX void showGraphConfig() { cmode = vid.xres > vid.yres * 1.4 ? sm::SIDE : sm::MAYDARK; gamescreen(0); @@ -1039,8 +1059,7 @@ EX void showGraphConfig() { dialog::addSelItem(XLAT("movement animation speed"), fts(vid.mspeed), 'm'); - dialog::addBoolItem(XLAT("extra graphical effects"), (vid.particles), 'u'); - dialog::addBoolItem(XLAT("background particle effects"), (vid.backeffects), 'p'); + dialog::addItem(XLAT("extra graphical effects"), 'u'); dialog::addBreak(50); dialog::addBack(); @@ -1055,7 +1074,7 @@ EX void showGraphConfig() { if((uni >= 32 && uni < 64) || uni == 'L' || uni == 'C') xuni = uni; - if(xuni == 'u') vid.particles = !vid.particles; + if(xuni == 'u') pushScreen(showSpecialEffects); else if(xuni == 'a') dialog::editNumber(vid.sspeed, -5, 5, 1, 0, XLAT("scrolling speed"), diff --git a/control.cpp b/control.cpp index f88c810c..c9fdc49b 100644 --- a/control.cpp +++ b/control.cpp @@ -1027,7 +1027,23 @@ EX bool gmodekeys(int sym, int uni) { if(NUMBERKEY == '7') { vid.darkhepta = !vid.darkhepta; return true; } if(GDIM == 2) { - if(NUMBERKEY == '1' && !rug::rugged) { vid.alpha = 999; vid.scale = 998; vid.xposition = vid.yposition = 0; } + if(among(NUMBERKEY, '1', '2', '3') && !rug::rugged && euclid && WDIM == 2) { + vid.xposition = vid.yposition = 0; + ld maxs = 0; + auto& cd = current_display; + for(auto& p: gmatrix) for(int i=0; itype; i++) { + hyperpoint h = tC0(p.second * currentmap->adj(p.first, i)); + hyperpoint onscreen; + applymodel(h, onscreen); + maxs = max(maxs, onscreen[0] / cd->xsize); + maxs = max(maxs, onscreen[1] / cd->ysize); + } + vid.alpha = 1; + vid.scale = vid.scale / 2 / maxs / cd->radius; + if(NUMBERKEY == '3') vid.scale *= 2; + if(NUMBERKEY == '1') vid.scale /= 2; + } + else if(NUMBERKEY == '1' && !rug::rugged) { vid.alpha = 999; vid.scale = 998; vid.xposition = vid.yposition = 0; } else if(NUMBERKEY == '2' && !rug::rugged) { vid.alpha = 1; vid.scale = 0.4; vid.xposition = vid.yposition = 0; } else if(NUMBERKEY == '3' && !rug::rugged) { vid.alpha = 1; vid.scale = 1; vid.xposition = vid.yposition = 0; } else if(NUMBERKEY == '4' && !rug::rugged) { vid.alpha = 0; vid.scale = 1; vid.xposition = vid.yposition = 0; } diff --git a/environment.cpp b/environment.cpp index 04a636df..dfa35106 100644 --- a/environment.cpp +++ b/environment.cpp @@ -348,6 +348,7 @@ EX void bfs() { if(c2->land == laWhirlwind) havewhat |= HF_WHIRLWIND; if(c2->land == laWestWall) havewhat |= HF_WESTWALL; if(c2->land == laPrairie) havewhat |= HF_RIVER; + if(c2->land == laClearing) havewhat |= HF_MUTANT; if(c2->wall == waRose) havewhat |= HF_ROSE; diff --git a/expansion.cpp b/expansion.cpp index 21027f4f..5f49f156 100644 --- a/expansion.cpp +++ b/expansion.cpp @@ -15,164 +15,6 @@ int subtype(cell *c) { return patterns::getpatterninfo(c, patterns::PAT_NONE, 0).id; } -#if HDR -struct bignum { - static const int BASE = 1000000000; - static const long long BASE2 = BASE * (long long)BASE; - vector digits; - bignum() {} - bignum(int i) : digits() { digits.push_back(i); } - void be(int i) { digits.resize(1); digits[0] = i; } - bignum& operator +=(const bignum& b); - void addmul(const bignum& b, int factor); - string get_str(int max_length); - - bool operator < (const bignum&) const; - - ld leading() const { - switch(isize(digits)) { - case 0: - return 0; - case 1: - return digits.back(); - default: - return digits.back() + ld(digits[isize(digits)-2]) / BASE; - } - } - - ld approx() const { - return leading() * pow(BASE, isize(digits) - 1); - } - - ld log_approx() const { - return log(leading()) * log(BASE) * (isize(digits) - 1); - } - - ld operator / (const bignum& b) const { - return leading() / b.leading() * pow(BASE, isize(digits) - isize(b.digits)); - } - - int approx_int() const { - if(isize(digits) > 1) return BASE; - if(digits.empty()) return 0; - return digits[0]; - } - - long long approx_ll() const { - if(isize(digits) > 2) return BASE2; - if(digits.empty()) return 0; - if(isize(digits) == 1) return digits[0]; - return digits[0] + digits[1] * (long long) BASE; - } - - friend inline bignum operator +(bignum a, const bignum& b) { a.addmul(b, 1); return a; } - friend inline bignum operator -(bignum a, const bignum& b) { a.addmul(b, -1); return a; } - }; -#endif - -bignum& bignum::operator +=(const bignum& b) { - int K = isize(b.digits); - if(K > isize(digits)) digits.resize(K); - int carry = 0; - for(int i=0; i= isize(digits)) digits.push_back(0); - digits[i] += carry; - if(i < K) digits[i] += b.digits[i]; - if(digits[i] >= BASE) { - digits[i] -= BASE; - carry = 1; - } - else carry = 0; - } - return *this; - } - -bool bignum::operator < (const bignum& b) const { - if(isize(digits) != isize(b.digits)) - return isize(digits) < isize(b.digits); - for(int i = isize(digits)-1; i>=0; i--) - if(digits[i] != b.digits[i]) - return digits[i] < b.digits[i]; - return false; - } - -void bignum::addmul(const bignum& b, int factor) { - int K = isize(b.digits); - if(K > isize(digits)) digits.resize(K); - int carry = 0; - for(int i=0; i 0 || carry < -1) || (carry == -1 && i < isize(digits)); i++) { - if(i >= isize(digits)) digits.push_back(0); - long long l = digits[i]; - l += carry; - if(i < K) l += b.digits[i] * factor; - carry = 0; - if(l >= BASE) carry = l / BASE; - if(l < 0) carry = -(BASE-1-l) / BASE; - l -= carry * BASE; - digits[i] = l; - } - if(carry < 0) digits.back() -= BASE; - while(isize(digits) && digits.back() == 0) digits.pop_back(); - } - -EX bignum hrand(bignum b) { - bignum res; - int d = isize(b.digits); - while(true) { - res.digits.resize(d); - for(int i=0; i=0; i--) { - if(isize(ret) > max_length && i) { - ret += XLAT(" (%1 more digits)", its(9 * (i+1))); - return ret; - } - - ret += " "; - string val = its(digits[i]); - while(isize(val) < 9) val = "0" + val; - ret += val; - } - return ret; - } - void canonicize(vector& t) { for(int i=2; i0 for particles color_t color; + string text; flashdata(int _t, int _s, cell *_w, color_t col, int sped) { t=_t; size=_s; where=_w; color = col; angle = rand() % 1000; spd = sped; @@ -3771,6 +3772,13 @@ struct flashdata { vector flashes; +EX void drawBubble(cell *c, color_t col, string s, ld size) { + auto fd = flashdata(ticks, 1000, c, col, 0); + fd.text = s; + fd.angle = size; + flashes.push_back(fd); + } + EX void drawFlash(cell *c) { flashes.push_back(flashdata(ticks, 1000, c, iinf[itOrbFlash].color, 0)); } @@ -3990,8 +3998,8 @@ EX void drawMarkers() { queuecircleat(lmouseover, .8, darkena(lmouseover->cpdist > 1 ? 0x00FFFF : 0xFF0000, 0, 0xFF)); } - if(pcm.mip.t && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON && WDIM == 2) { - queuecircleat(pcm.mip.t, .6, darkena(0xFFD500, 0, 0xFF)); + if(global_pushto && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON && WDIM == 2) { + queuecircleat(global_pushto, .6, darkena(0xFFD500, 0, 0xFF)); } #endif @@ -4137,8 +4145,20 @@ EX void draw_flash(struct flashdata& f, const transmatrix& V, bool& kill) { int tim = ticks - f.t; if(tim <= f.size && !f.spd) kill = false; - - if(f.spd) { + + if(f.text != "") { + if(GDIM == 3 || sphere) + queuestr(V, (1 - tim * 1. / f.size) * f.angle, f.text, f.color); + else if(!kill) { + hyperpoint h = tC0(V); + if(hdist0(h) > .1) { + transmatrix V2 = rspintox(h) * xpush(hdist0(h) * (1 / (1 - tim * 1. / f.size))); + queuestr(V2, f.angle, f.text, f.color); + } + } + } + + else if(f.spd) { #if CAP_SHAPES if(tim <= 300) kill = false; int partcol = darkena(f.color, 0, GDIM == 3 ? 255 : max(255 - tim*255/300, 0)); @@ -4786,7 +4806,7 @@ EX bool nohelp; EX void normalscreen() { help = "@"; - mouseovers = XLAT("Press F1 or right click for help"); + mouseovers = standard_help(); #if CAP_TOUR if(tour::on) mouseovers = tour::tourhelp; diff --git a/help.cpp b/help.cpp index aaaabec0..269e2cbc 100644 --- a/help.cpp +++ b/help.cpp @@ -178,6 +178,10 @@ void buildHelpText() { #endif } +EX string standard_help() { + return XLAT("Press F1 or right click for help"); + } + EX void buildCredits() { help = ""; help += XLAT("game design, programming, texts and graphics by Zeno Rogue \n\n"); @@ -576,6 +580,16 @@ EX string generateHelpForMonster(eMonster m) { if(isGhost(m)) s += XLAT("\n\nA Ghost never moves to a cell which is adjacent to another Ghost of the same kind.", m); + if(m == moMutant) { + using namespace clearing; + if(direct) + s += XLAT("\n\nLeaves cut directly: %1", its(direct)); + if(kills[moMutant]) + s += XLAT("\n\nLeaves cut onscreen: %1", its(kills[moMutant])); + if(imputed.nonzero()) + s += XLAT("\n\nLeaves cut offscreen (approximately): %1", imputed.get_str(10000)); + } + if(m == moBat || m == moEagle) s += XLAT("\n\nFast flying creatures may attack or go against gravity only in their first move.", m); diff --git a/hud.cpp b/hud.cpp index 38476004..0ae19507 100644 --- a/hud.cpp +++ b/hud.cpp @@ -253,8 +253,25 @@ bool displayglyph(int cx, int cy, int buttonsize, char glyph, color_t color, int (qty < 10 && (flags & (GLYPH_MARKTODO | GLYPH_RUNOUT))) ? buttonsize*3/4 : qty < 100 ? buttonsize / 2 : buttonsize / 3; - if(str != "") - displayfr(cx + buttonsize, cy + buttonsize/2 - bsize/2, 1, bsize, str, color, 16); + + if(id == moMutant + ittypes && clearing::imputed.nonzero()) { + ld d = qty + clearing::imputed.approx_ld(); + if(d < 100000) str = its(int(d)); + else { + int digits = 0; + while(d >= 10) digits++, d /= 10; + str = its(int(d*100)) + "E" + its(digits); + str.insert(1, "."); + } + bsize = buttonsize / 4; + } + + if(str != "") { + if(textwidth(bsize, str) < buttonsize) + displayfr(cx + buttonsize, cy + buttonsize/2 - bsize/2, 1, bsize, str, color, 16); + else + displayfr(cx, cy + buttonsize/2 - bsize/2, 1, bsize, str, color, 0); + } return b; } @@ -365,6 +382,8 @@ EX bool nofps = false; EX color_t crosshair_color = 0xFFFFFFC0; EX ld crosshair_size = 0; +EX bool long_kills; + EX void drawStats() { if(nohud || vid.stereo_mode == sLR) return; if(callhandlers(false, hooks_prestats)) return; @@ -577,7 +596,8 @@ EX void drawStats() { #endif } else if(!peace::on) { - if(displayButtonS(vid.xres - 8, vid.fsize, XLAT("score: %1", its(gold())), forecolor, 16, vid.fsize)) { + string scoreline = XLAT("score: %1", its(gold())); + if(displayButtonS(vid.xres - 8, vid.fsize, scoreline, forecolor, 16, vid.fsize)) { mouseovers = XLAT("Your total wealth"), instat = true, getcstat = SDLK_F1, @@ -590,16 +610,29 @@ EX void drawStats() { "Orbs of Yendor are worth 50 $$$ each.\n\n" ); } - if(displayButtonS(8, vid.fsize, XLAT("kills: %1", its(tkills())), forecolor, 0, vid.fsize)) { - instat = true, - getcstat = SDLK_F1, - mouseovers = XLAT("Your total kills")+": " + its(tkills()), - help = helptitle(XLAT("Your total kills") + ": " + its(tkills()), 0x404040) + - XLAT( - "In most lands, more treasures are generated with each enemy native to this land you kill. " - "Moreover, 100 kills is a requirement to enter the Graveyard and the Hive.\n\n" - "Friendly creatures and parts of monsters (such as the Ivy) do appear in the list, " - "but are not counted in the total kill count."); + string s = XLAT("kills: %1", its(tkills())); + long_kills = false; + int siz = vid.fsize; + if(cwt.at->land == laClearing && clearing::imputed.approx_ld() >= 100000) { + long_kills = true; + s = XLAT("leaves cut: " + (bignum(kills[moMutant]) + clearing::imputed).get_str(200)); + if(mouseovers == standard_help()) mouseovers = " "; + while(siz > 4 && textwidth(siz, s) > vid.xres - textwidth(vid.fsize, scoreline)) siz--; + } + + if(displayButtonS(8, vid.fsize, s, forecolor, 0, siz)) { + instat = true; + getcstat = SDLK_F1; + if(long_kills) { mouseovers = " "; help = generateHelpForMonster(moMutant); } + else { + mouseovers = XLAT("Your total kills")+": " + its(tkills()), + help = helptitle(XLAT("Your total kills") + ": " + its(tkills()), 0x404040) + + XLAT( + "In most lands, more treasures are generated with each enemy native to this land you kill. " + "Moreover, 100 kills is a requirement to enter the Graveyard and the Hive.\n\n" + "Friendly creatures and parts of monsters (such as the Ivy) do appear in the list, " + "but are not counted in the total kill count."); + } } } string vers = VER; diff --git a/hyper.h b/hyper.h index ee808260..302c43ce 100644 --- a/hyper.h +++ b/hyper.h @@ -246,7 +246,7 @@ struct videopar { ld xposition, yposition; bool grid; - int particles; + bool particles; int fsize; int flashtime; @@ -325,6 +325,7 @@ struct videopar { ld collignon_parameter; bool collignon_reflected; ld plevel_factor; + bool bubbles_special, bubbles_threshold, bubbles_all; }; extern videopar vid; diff --git a/landgen.cpp b/landgen.cpp index d77f4b85..0ff8bbfb 100644 --- a/landgen.cpp +++ b/landgen.cpp @@ -1157,7 +1157,9 @@ EX void giantLandSwitch(cell *c, int d, cell *from) { (hrand(50+items[itMutant]/2+yendor::hardness()) < 30) ? (hrand(100) < 50 ? waBigTree : waSmallTree) : waNone; } if(d == 8) { - if(hrand(doCross ?450:15000) < 20 + (2 * items[itMutant] + yendor::hardness()) && !safety) { + bool ok = c->landparam == 0; + forCellEx(c2, c) if(c2->landparam) ok = false; + if(ok && hrand(doCross ?450:15000) < 20 + (2 * items[itMutant] + yendor::hardness()) && !safety) { if(!peace::on) c->item = itMutant; c->landparam = items[itMutant] + 5 + hrand(11); c->wall = waNone; @@ -1165,7 +1167,7 @@ EX void giantLandSwitch(cell *c, int d, cell *from) { if(c->move(i) && (c->move(i)->wall == waBigTree || c->move(i)->wall == waSmallTree)) c->move(i)->wall = waNone; } - else if(hrand_monster(15000) < 20 + (2 * items[itMutant] + yendor::hardness()) && !safety) { + else if(hrand_monster(15000) < 20 + (2 * items[itMutant] + yendor::hardness()) && ok && !safety) { // for the Yendor Challenge, use only Mutants if(!(yendor::on && yendor::clev().l == laMirror)) { c->monst = moForestTroll; diff --git a/monstergen.cpp b/monstergen.cpp index afcbafc2..6592106a 100644 --- a/monstergen.cpp +++ b/monstergen.cpp @@ -38,6 +38,7 @@ EX int buildIvy(cell *c, int children, int minleaf) { leaf += leafchild; if(leaf < minleaf) { if(child) killIvy(child, moNone); + dynamicval k(kills[moIvyDead]); killIvy(c, moNone); return 0; } diff --git a/monstermove.cpp b/monstermove.cpp index c896b365..49a25206 100644 --- a/monstermove.cpp +++ b/monstermove.cpp @@ -1333,8 +1333,18 @@ EX void movehex_rest(bool mounted) { } EX void movemutant() { + manual_celllister mcells; + for(cell *c: currentmap->allcells()) mcells.add(c); + if(!bounded) + for(int i=0; iland == laClearing && c->monst != moMutant && !pseudohept(c)) + forCellEx(c2, c) forCellEx(c3, c2) if(celldistAlt(c3) < celldistAlt(c)) + mcells.add(c3); + } + vector young; - for(cell *c: currentmap->allcells()) + for(cell *c: mcells.lst) if(c->monst == moMutant && c->stuntime == mutantphase) young.push_back(c); diff --git a/multi.cpp b/multi.cpp index a796f69c..622c259d 100644 --- a/multi.cpp +++ b/multi.cpp @@ -958,6 +958,11 @@ EX void handleInput(int delta) { cdir = d; if(multi::multiPlayerTarget(i) == c) break; cdir = scdir; + cwt = multi::player[i]; + calcMousedest(); + auto& sd = multi::whereto[i].subdir; + sd = mousedest.subdir; + if(sd == 0) sd = 1; } } } diff --git a/pcmove.cpp b/pcmove.cpp index 41d9a880..b543f0ec 100644 --- a/pcmove.cpp +++ b/pcmove.cpp @@ -224,12 +224,15 @@ struct pcmove { }; #endif -EX pcmove pcm; +EX cell *global_pushto; EX bool movepcto(int d, int subdir IS(1), bool checkonly IS(false)) { + pcmove pcm; pcm.checkonly = checkonly; pcm.d = d; pcm.subdir = subdir; - return pcm.movepcto(); + auto b = pcm.movepcto(); + global_pushto = pcm.mip.t; + return b; } bool pcmove::movepcto() { diff --git a/system.cpp b/system.cpp index 840f2bea..f3b6c4f9 100644 --- a/system.cpp +++ b/system.cpp @@ -394,7 +394,7 @@ bool havesave = true; #if HDR #define MAXBOX 500 -#define POSSCORE 371 // update this when new boxes are added! +#define POSSCORE 373 // update this when new boxes are added! struct score { string ver; int box[MAXBOX]; @@ -414,6 +414,16 @@ void applyBox(int& t) { else boxid++; } +void applyBoxBignum(bignum& tb) { + float tf; + int ti; + if(saving) tf = tb.approx_ld(); + if(saving) memcpy(&ti, &tf, 4); + applyBox(ti); + if(loading) memcpy(&tf, &ti, 4); + if(loading) tb = bignum(tf); + } + EX void applyBoxNum(int& i, string name IS("")) { fakebox[boxid] = (name == ""); boxname[boxid] = name; @@ -814,6 +824,9 @@ EX void applyBoxes() { applyBoxM(moNarciss); applyBoxM(moMirrorSpirit); + + applyBox(clearing::direct); + applyBoxBignum(clearing::imputed); if(POSSCORE != boxid) printf("ERROR: %d boxes\n", boxid); } diff --git a/util.cpp b/util.cpp index de5249e0..58d412d2 100644 --- a/util.cpp +++ b/util.cpp @@ -325,4 +325,198 @@ EX string parser_help() { "(a)sin(h), (a)cos(h), (a)tan(h), exp, log, abs, re, im, conj, let(t=...,...t...), floor, frac, e, i, pi, s, ms, mousex, mousey, mousez, shot [1 if taking screenshot/animation], sqrt, to01, random, edge(7,3), regradius(7,3), ifp(a,v,w) [if positive]"); } +#if HDR +struct bignum { + static const int BASE = 1000000000; + static const long long BASE2 = BASE * (long long)BASE; + vector digits; + bignum() {} + bignum(int i) : digits() { digits.push_back(i); } + void be(int i) { digits.resize(1); digits[0] = i; } + bignum& operator +=(const bignum& b); + void addmul(const bignum& b, int factor); + string get_str(int max_length) const; + bignum(ld d); + + bool operator < (const bignum&) const; + bool operator > (const bignum& b) const { return b < self; } + + ld leading() const { + switch(isize(digits)) { + case 0: + return 0; + case 1: + return digits.back(); + default: + return digits.back() + ld(digits[isize(digits)-2]) / BASE; + } + } + + ld approx() const { + return leading() * pow(BASE, isize(digits) - 1); + } + + ld log_approx() const { + return log(leading()) * log(BASE) * (isize(digits) - 1); + } + + ld approx_div(const bignum& b) const { + return leading() / b.leading() * pow(BASE, isize(digits) - isize(b.digits)); + } + + int approx_int() const { + if(isize(digits) > 1) return BASE; + if(digits.empty()) return 0; + return digits[0]; + } + + bool nonzero() { return approx_ld() != 0; } + + bignum randomized_div(int x) const; + + ld approx_ld() const { + ld res = 0; + for(int i=0; i 2) return BASE2; + if(digits.empty()) return 0; + if(isize(digits) == 1) return digits[0]; + return digits[0] + digits[1] * (long long) BASE; + } + + friend inline bignum operator +(bignum a, const bignum& b) { a.addmul(b, 1); return a; } + friend inline bignum operator -(bignum a, const bignum& b) { a.addmul(b, -1); return a; } + }; +#endif + +bignum& bignum::operator +=(const bignum& b) { + int K = isize(b.digits); + if(K > isize(digits)) digits.resize(K); + int carry = 0; + for(int i=0; i= isize(digits)) digits.push_back(0); + digits[i] += carry; + if(i < K) digits[i] += b.digits[i]; + if(digits[i] >= BASE) { + digits[i] -= BASE; + carry = 1; + } + else carry = 0; + } + return *this; + } + +bool bignum::operator < (const bignum& b) const { + if(isize(digits) != isize(b.digits)) + return isize(digits) < isize(b.digits); + for(int i = isize(digits)-1; i>=0; i--) + if(digits[i] != b.digits[i]) + return digits[i] < b.digits[i]; + return false; + } + +bignum bignum::randomized_div(int x) const { + bignum res = self; + long long carry = 0; + int K = isize(res.digits); + for(int i=K-1; i>=0; i--) { + carry *= BASE; + carry += digits[i]; + tie(carry, res.digits[i]) = make_pair(carry % x, carry / x); + } + while(isize(res.digits) && res.digits.back() == 0) res.digits.pop_back(); + if(rand() % x < carry) res += 1; + println(hlog, get_str(100), " / ", x, " = ", res.get_str(100)); + return res; + } + +void bignum::addmul(const bignum& b, int factor) { + int K = isize(b.digits); + if(K > isize(digits)) digits.resize(K); + int carry = 0; + for(int i=0; i 0 || carry < -1) || (carry == -1 && i < isize(digits)); i++) { + if(i >= isize(digits)) digits.push_back(0); + long long l = digits[i]; + l += carry; + if(i < K) l += b.digits[i] * factor; + carry = 0; + if(l >= BASE) carry = l / BASE; + if(l < 0) carry = -(BASE-1-l) / BASE; + l -= carry * BASE; + digits[i] = l; + } + if(carry < 0) digits.back() -= BASE; + while(isize(digits) && digits.back() == 0) digits.pop_back(); + } + +EX bignum hrand(bignum b) { + bignum res; + int d = isize(b.digits); + while(true) { + res.digits.resize(d); + for(int i=0; i=0; i--) { + if(isize(ret) > max_length && i) { + ret += XLAT(" (%1 more digits)", its(9 * (i+1))); + return ret; + } + + ret += " "; + string val = its(digits[i]); + while(isize(val) < 9) val = "0" + val; + ret += val; + } + return ret; + } + +bignum::bignum(ld d) { + if(d == 0) return; + int n = 1; + while(d > BASE) d /= BASE, n++; + digits.resize(n); + n--; + while(n >= 0) { digits[n] = int(d); d -= digits[n]; d *= BASE; n--; } + } + }