From 2bc66342ddb23d56c9196e55424fbceb0ed187ae Mon Sep 17 00:00:00 2001 From: Zeno Rogue Date: Sun, 23 Dec 2018 03:14:48 +0100 Subject: [PATCH] newlands:: implemented new Orbs: Gravity, Intensity, Choice --- classes.cpp | 7 +- classes.h | 5 +- complex2.cpp | 6 +- expansion.cpp | 15 ++++ game.cpp | 210 ++++++++++++++++++++++++++++++++++++++++---------- graph.cpp | 82 +++++++++++++++++++- hyper.h | 6 ++ inventory.cpp | 4 + orbgen.cpp | 3 + orbs.cpp | 10 +++ 10 files changed, 300 insertions(+), 48 deletions(-) diff --git a/classes.cpp b/classes.cpp index b7bc4ac6..23df0463 100644 --- a/classes.cpp +++ b/classes.cpp @@ -1245,7 +1245,12 @@ itemtype iinf[ittypes] = { "Tasty cookie." }, { '*', 0x20C0C0, "West Treasure", NODESCYET}, - { '*', 0xC020C0, "Variant Treasure", NODESCYET} + { '*', 0xC020C0, "Variant Treasure", NODESCYET}, + + { 'o', 0x703800, "Orb of Intensity", NODESCYET}, + { 'o', 0x80D080, "Orb of Gravity", NODESCYET}, + { 'o', 0xD08080, "Orb of Choice", NODESCYET}, + // { '*', 0x26619C, "Lapis Lazuli", NODESCYET}, }; diff --git a/classes.h b/classes.h index 170efdcb..e3095723 100644 --- a/classes.h +++ b/classes.h @@ -84,7 +84,7 @@ struct genderswitch_t { #define NUM_GS 6 -static const int ittypes = 133; +static const int ittypes = 136; struct itemtype { char glyph; @@ -131,7 +131,8 @@ enum eItem { itOrbLava, itOrbMorph, itGlowCrystal, itSnake, itDock, itRuins, itMagnet, itSwitch, itOrbPhasing, itOrbMagnetism, itOrbSlaying, - itBrownian, itWest, itVarTreasure + itBrownian, itWest, itVarTreasure, + itOrbBrown, itOrbGravity, itOrbChoice }; static const int walltypes = 111; diff --git a/complex2.cpp b/complex2.cpp index b8e2b88a..8fcf4686 100644 --- a/complex2.cpp +++ b/complex2.cpp @@ -157,7 +157,7 @@ namespace westwall { vector whirlline; int d = coastvalEdge(c); whirlline.push_back(c); - whirlline.push_back(ts::left_of(c, coastvalEdge1)); + whirlline.push_back(gravity_state == gsAnti ? ts::right_of(c, coastvalEdge1) : ts::left_of(c, coastvalEdge1)); build(whirlline, d); reverse(whirlline.begin(), whirlline.end()); build(whirlline, d); @@ -178,6 +178,7 @@ namespace westwall { void move() { manual_celllister cl; + if(gravity_state == gsLevitation) return; for(cell *c: dcal) moveAt(c, cl); // Keys and Orbs of Yendor always move using namespace yendor; @@ -186,7 +187,8 @@ namespace westwall { // println(hlog, "coastval of actual key is ", coastvalEdge1(yi[i].actual_key()), " and item is ", dnameof(yi[i].actual_key()->item), "and mpdist is ", yi[i].actual_key()->mpdist); moveAt(yi[i].actual_key(), cl); if(yi[i].actualKey) { - yi[i].age++; + if(gravity_state == gsAnti) yi[i].age--; + else yi[i].age++; setdist(yi[i].actual_key(), 8, NULL); } } diff --git a/expansion.cpp b/expansion.cpp index 85a8250a..d1b215e9 100644 --- a/expansion.cpp +++ b/expansion.cpp @@ -78,6 +78,21 @@ void operator ++(bignum &b, int) { } } +void operator --(bignum &b, int) { + int i = 0; + while(true) { + if(isize(b.digits) == i) { b.digits.push_back(bignum::BASE-1); break; } + else if(b.digits[i] == 0) { + b.digits[i] = bignum::BASE-1; + i++; + } + else { + b.digits[i]--; + break; + } + } + } + string bignum::get_str(int max_length) { if(digits.empty()) return "0"; string ret = its(digits.back()); diff --git a/game.cpp b/game.cpp index 39a479b4..2e5e7c6f 100644 --- a/game.cpp +++ b/game.cpp @@ -196,7 +196,7 @@ void initcell(cell *c) { bool doesnotFall(cell *c) { if(c->wall == waChasm) return false; - else if(cellUnstable(c)) { + else if(cellUnstable(c) && !in_gravity_zone(c)) { fallingFloorAnimation(c); c->wall = waChasm; return false; @@ -393,6 +393,68 @@ int killtypes() { return res; } +eGravity gravity_state, last_gravity_state; + +bool bird_disruption(cell *c) { + return c->cpdist <= 5 && items[itOrbGravity]; + } + +bool in_gravity_zone(cell *c) { + return gravity_state && c->cpdist <= 5; + } + +int gravity_zone_diff(cell *c) { + if(in_gravity_zone(c)) { + if(gravity_state == gsLevitation) return 0; + if(gravity_state == gsAnti) return -1; + } + return 1; + } + +bool isJWall(cell *c) { + return isWall(c) || c->monst == passive_switch; + } + +eGravity get_static_gravity(cell *c) { + if(isGravityLand(c->land)) + return gsLevitation; + if(among(c->wall, waArrowTrap, waFireTrap, waClosePlate, waOpenPlate, waTrapdoor)) + return gsNormal; + forCellEx(c2, c) if(isJWall(c2)) + return gsAnti; + if(isWatery(c) || isChasmy(c) || among(c->wall, waMagma, waMineUnknown, waMineMine, waMineOpen)) + return gsLevitation; + return gsNormal; + } + +eGravity get_move_gravity(cell *c, cell *c2) { + if(isGravityLand(c->land) && isGravityLand(c2->land)) { + int d = gravityLevelDiff(c, c2); + if(d > 0) return gsNormal; + if(d == 0) return gsLevitation; + if(d < 0) return gsAnti; + return gsNormal; + } + else { + if(snakelevel(c) != snakelevel(c2)) { + int d = snakelevel(c2) - snakelevel(c); + if(d > 0) return gsAnti; + if(d == -3) return gsLevitation; + return gsNormal; + } + forCellEx(c3, c) if(isJWall(c3)) + return gsAnti; + forCellEx(c3, c2) if(isJWall(c3)) + return gsAnti; + if(isWatery(c2) && c->wall == waBoat && !againstCurrent(c2, c)) + return gsNormal; + if(isWatery(c2) || isChasmy(c2) || among(c2->wall, waMagma, waMineUnknown, waMineMine, waMineOpen) || anti_alchemy(c2, c)) + return gsLevitation; + return gsNormal; + } + } + + bool isWarped(cell *c) { return isWarped(c->land) || (!inmirrororwall(c->land) && (items[itOrb37] && c->cpdist <= 4)); } @@ -486,7 +548,13 @@ bool checkflags(flagtype flags, int x) { bool strictlyAgainstGravity(cell *w, cell *from, bool revdir, flagtype flags) { return cellEdgeUnstable(w, flags) && cellEdgeUnstable(from, flags) && - !(shmup::on && from == w) && gravityLevelDiff(w, from) != (revdir?1:-1); + !(shmup::on && from == w) && gravityLevelDiff(from, w) != (revdir?-1:1) * gravity_zone_diff(from); + } + +bool anti_alchemy(cell *w, cell *from) { + bool alch1 = w->wall == waFloorA && from && from->wall == waFloorB && !w->item && !from->item; + alch1 |= w->wall == waFloorB && from && from->wall == waFloorA && !w->item && !from->item; + return alch1; } bool passable(cell *w, cell *from, flagtype flags) { @@ -503,6 +571,10 @@ bool passable(cell *w, cell *from, flagtype flags) { if(from && !((flags & P_ISPLAYER) && pp->monst)) { int i = vrevdir ? incline(w, from) : incline(from, w); + if(in_gravity_zone(w)) { + if(gravity_state == gsLevitation) i = 0; + if(gravity_state == gsAnti && i > 1) i = 1; + } if(i < -1 && F(P_ROSE)) return false; if((i > 1) && !F(P_JUMP1 | P_JUMP2 | P_BULLET | P_FLYING | P_BLOW | P_CLIMBUP | P_AETHER | P_REPTILE)) return false; @@ -528,12 +600,12 @@ bool passable(cell *w, cell *from, flagtype flags) { if(!shmup::on && sword::at(w, flags & P_ISPLAYER) && !F(P_DEADLY | P_BULLET | P_ROSE)) return false; - bool alch1 = w->wall == waFloorA && from && from->wall == waFloorB && !w->item && !from->item; - alch1 |= w->wall == waFloorB && from && from->wall == waFloorA && !w->item && !from->item; + bool alch1 = anti_alchemy(w, from); if(alch1) { - bool alchok = F(P_JUMP1 | P_JUMP2 | P_FLYING | P_TELE | P_BLOW | P_AETHER | P_BULLET) - && !F(P_ROSE); + bool alchok = (in_gravity_zone(w) || in_gravity_zone(from)); + alchok = alchok || (F(P_JUMP1 | P_JUMP2 | P_FLYING | P_TELE | P_BLOW | P_AETHER | P_BULLET) + && !F(P_ROSE)); if(!alchok) return false; } @@ -590,16 +662,27 @@ bool passable(cell *w, cell *from, flagtype flags) { if(isThorny(w->wall) && F(P_BLOW | P_DEADLY)) return true; if(isFire(w) || w->wall == waMagma) { - if(!F(P_AETHER | P_WINTER | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY)) return false; + if(w->wall == waMagma && in_gravity_zone(w)) ; + else if(!F(P_AETHER | P_WINTER | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY)) return false; + } + + if(in_gravity_zone(w) && gravity_state == gsAnti && !isGravityLand(w->land) && (!from || !isGravityLand(from->land))) + if(!F(P_AETHER | P_BLOW | P_JUMP1 | P_BULLET | P_FLYING)) { + bool next_to_wall = false; + forCellEx(c2, w) if(isJWall(c2)) next_to_wall = true; + if(from) forCellEx(c2, from) if(isJWall(c2)) next_to_wall = true; + if(!next_to_wall && (!from || incline(from, w) * (vrevdir?-1:1) <= 0)) return false; } if(isWatery(w)) { - if(from && from->wall == waBoat && F(P_USEBOAT) && + if(in_gravity_zone(w)) ; + else if(from && from->wall == waBoat && F(P_USEBOAT) && (!againstCurrent(w, from) || F(P_MARKWATER))) ; else if(!F(P_AETHER | P_FISH | P_FLYING | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY | P_REPTILE)) return false; } if(isChasmy(w)) { - if(!F(P_AETHER | P_FLYING | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY | P_REPTILE)) return false; + if(in_gravity_zone(w)) ; + else if(!F(P_AETHER | P_FLYING | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY | P_REPTILE)) return false; } if(w->wall == waRoundTable && from && from->wall != waRoundTable && (flags & P_ISPLAYER)) return true; @@ -843,8 +926,12 @@ bool passable_for(eMonster m, cell *w, cell *from, flagtype extra) { return passable(w, from, extra | P_FLYING | P_ISFRIEND); if(m == moHexSnake) return !pseudohept(w) && passable(w, from, extra|P_WIND|P_FISH); - if(isBird(m)) - return passable(w, from, extra | P_FLYING); + if(isBird(m)) { + if(bird_disruption(w) && (!from || bird_disruption(from)) && markOrb(itOrbGravity)) + return passable(w, from, extra); + else + return passable(w, from, extra | P_FLYING); + } if(m == moReptile) return passable(w, from, extra | P_REPTILE); if(isDragon(m)) @@ -2570,14 +2657,17 @@ int gravityLevel(cell *c) { int gravityLevelDiff(cell *c, cell *d) { if(c->land != laWestWall || d->land != laWestWall) return gravityLevel(c) - gravityLevel(d); - int bonus = 0; - int id = parent_id(c, 1, coastvalEdge); - for(int a=0; a<3; a++) - if(c->modmove(id+a) == d) bonus++; - id = parent_id(c, -1, coastvalEdge); - for(int a=0; a<3; a++) - if(c->modmove(id-a) == d) bonus--; - return bonus; + + int nid = neighborId(c, d); + int id1 = parent_id(c, 1, coastvalEdge) + 1; + int di1 = angledist(c->type, id1, nid); + + int id2 = parent_id(c, -1, coastvalEdge) - 1; + int di2 = angledist(c->type, id2, nid); + + if(di1 < di2) return 1; + if(di1 > di2) return -1; + return 0; } bool canUnstable(eWall w, flagtype flags) { @@ -2591,7 +2681,7 @@ bool cellEdgeUnstable(cell *c, flagtype flags) { for(int i=0; itype; i++) if(c->move(i)) { if(isAnyIvy(c->move(i)->monst) && c->land == laMountain && !(flags & MF_IVY)) return false; - if(gravityLevelDiff(c, c->move(i)) == 1) { + if(gravityLevelDiff(c, c->move(i)) == gravity_zone_diff(c)) { if(againstWind(c->move(i), c)) return false; if(!passable(c->move(i), NULL, P_MONSTER | P_DEADLY)) return false; @@ -3363,20 +3453,20 @@ void moveEffect(cell *ct, cell *cf, eMonster m, int direction_hint) { if(!isNonliving(m)) terracottaAround(ct); - if(ct->wall == waMineUnknown && !ct->item && !ignoresPlates(m)) + if(ct->wall == waMineUnknown && !ct->item && !ignoresPlates(m) && normal_gravity_at(ct)) ct->landparam |= 2; // mark as safe - if((ct->wall == waClosePlate || ct->wall == waOpenPlate) && !ignoresPlates(m)) + if((ct->wall == waClosePlate || ct->wall == waOpenPlate) && !ignoresPlates(m) && normal_gravity_at(ct)) toggleGates(ct, ct->wall); - if(m == moDeadBird && cf == ct && cellUnstable(cf)) { + if(m == moDeadBird && cf == ct && cellUnstable(cf) && normal_gravity_at(ct)) { fallingFloorAnimation(cf); cf->wall = waChasm; } - if(ct->wall == waArrowTrap && !ignoresPlates(m)) + if(ct->wall == waArrowTrap && !ignoresPlates(m) && normal_gravity_at(ct)) activateArrowTrap(ct); - if(ct->wall == waFireTrap && !ignoresPlates(m) && ct->wparam == 0) + if(ct->wall == waFireTrap && !ignoresPlates(m) && ct->wparam == 0 && normal_gravity_at(ct)) ct->wparam = 1; if(cf && isPrincess(m)) princess::move(ct, cf); @@ -3452,16 +3542,16 @@ void playerMoveEffects(cell *c1, cell *c2) { sword::angle[multi::cpid] = sword::shift(c1, c2, sword::angle[multi::cpid]); destroyWeakBranch(c1, c2, moPlayer); - + uncoverMinesFull(c2); - if((c2->wall == waClosePlate || c2->wall == waOpenPlate) && !markOrb(itOrbAether)) + if((c2->wall == waClosePlate || c2->wall == waOpenPlate) && normal_gravity_at(c2) && !markOrb(itOrbAether)) toggleGates(c2, c2->wall); - if(c2->wall == waArrowTrap && c2->wparam == 0 && !markOrb(itOrbAether)) + if(c2->wall == waArrowTrap && c2->wparam == 0 && normal_gravity_at(c2) && !markOrb(itOrbAether)) activateArrowTrap(c2); - if(c2->wall == waFireTrap && c2->wparam == 0 && !markOrb(itOrbAether)) + if(c2->wall == waFireTrap && c2->wparam == 0 && normal_gravity_at(c2) &&!markOrb(itOrbAether)) c2->wparam = 1; princess::playernear(c2); @@ -3718,7 +3808,7 @@ void moveMonster(cell *ct, cell *cf, int direction_hint) { ct->stuntime = 2; else if(inc == 3 && ct->monst == moReptile) ct->stuntime = 3; - else if(inc == -3 && !survivesFall(ct->monst)) { + else if(inc == -3 && !survivesFall(ct->monst) && !passable(cf, ct, P_MONSTER)) { addMessage(XLAT("%The1 falls!", ct->monst)); fallMonster(ct, AF_FALL); } @@ -4664,6 +4754,7 @@ void groupmove2(cell *c, cell *from, int d, eMonster movtype, flagtype mf) { if(!ignoresSmell(c->monst) && againstRose(c, from)) return; if((mf & MF_ONLYEAGLE) && c->monst != moEagle && c->monst != moBat) return; + if((mf & MF_ONLYEAGLE) && bird_disruption(c) && markOrb(itOrbGravity)) return; // in the gravity lands, eagles cannot ascend in their second move if((mf & MF_ONLYEAGLE) && gravityLevelDiff(c, from) < 0) { onpath(c, 0); @@ -4742,7 +4833,7 @@ void groupmove(eMonster movtype, flagtype mf) { groupmove2(c->move(t),c,t,movtype,mf); } - if(movtype == moEagle && c->monst == moNone && !isPlayerOn(c)) { + if(movtype == moEagle && c->monst == moNone && !isPlayerOn(c) && !bird_disruption(c)) { cell *c2 = whirlwind::jumpFromWhereTo(c, false); groupmove2(c2, c, NODIR, movtype, mf); } @@ -5664,6 +5755,10 @@ bool saved_tortoise_on(cell *c) { !((tortoise::getb(c) ^ tortoise::babymap[c]) & tortoise::mask)); } +bool normal_gravity_at(cell *c) { + return !in_gravity_zone(c); + } + void moverefresh(bool turn = true) { int dcs = isize(dcal); @@ -5761,7 +5856,7 @@ void moverefresh(bool turn = true) { if(c->wall == waChasm) { if(c->land != laWhirlwind) c->item = itNone; - if(c->monst && !survivesChasm(c->monst) && c->monst != moReptile) { + if(c->monst && !survivesChasm(c->monst) && c->monst != moReptile && normal_gravity_at(c)) { if(c->monst != moRunDog && c->land == laMotion) achievement_gain("FALLDEATH1"); addMessage(XLAT("%The1 falls!", c->monst)); @@ -5814,7 +5909,7 @@ void moverefresh(bool turn = true) { else if(isWatery(c)) { if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM) c->monst = moGreaterShark; - if(c->monst && !survivesWater(c->monst)) { + if(c->monst && !survivesWater(c->monst) && normal_gravity_at(c)) { playSound(c, "splash"+pick12()); if(isNonliving(c->monst)) addMessage(XLAT("%The1 sinks!", c->monst)); @@ -5828,7 +5923,7 @@ void moverefresh(bool turn = true) { } } else if(c->wall == waSulphur || c->wall == waSulphurC || c->wall == waMercury) { - if(c->monst && !survivesPoison(c->monst, c->wall)) { + if(c->monst && !survivesPoison(c->monst, c->wall) && normal_gravity_at(c)) { playSound(c, "splash"+pick12()); if(isNonliving(c->monst)) addMessage(XLAT("%The1 sinks!", c->monst)); @@ -5843,7 +5938,7 @@ void moverefresh(bool turn = true) { } else if(c->wall == waMagma) { if(c->monst == moSalamander) c->stuntime = max(c->stuntime, 1); - else if(c->monst && !survivesPoison(c->monst, c->wall)) { + else if(c->monst && !survivesPoison(c->monst, c->wall) && normal_gravity_at(c)) { if(isNonliving(c->monst)) addMessage(XLAT("%The1 is destroyed by lava!", c->monst)); else @@ -6121,6 +6216,7 @@ bool checkNeedMove(bool checkonly, bool attacking) { else if(cwt.at->wall == waLake) { if(markOrb2(itOrbAether)) return false; if(markOrb2(itOrbFish)) return false; + if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false; if(checkonly) return true; flags |= AF_FALL; addMessage(XLAT("Ice below you is melting! RUN!")); @@ -6128,11 +6224,13 @@ bool checkNeedMove(bool checkonly, bool attacking) { else if(!attacking && cellEdgeUnstable(cwt.at)) { if(markOrb2(itOrbAether)) return false; if(checkonly) return true; + if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false; addMessage(XLAT("Nothing to stand on here!")); } else if(cwt.at->wall == waSea || cwt.at->wall == waCamelotMoat) { if(markOrb(itOrbFish)) return false; if(markOrb2(itOrbAether)) return false; + if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false; if(checkonly) return true; addMessage(XLAT("You have to run away from the water!")); } @@ -6148,11 +6246,13 @@ bool checkNeedMove(bool checkonly, bool attacking) { } else if(cwt.at->wall == waMagma && !markOrb(itOrbWinter) && !markOrb2(itOrbShield)) { if(markOrb2(itOrbAether)) return false; + if(in_gravity_zone(cwt.at) && passable(cwt.at, cwt.at, P_ISPLAYER)) return false; if(checkonly) return true; addMessage(XLAT("Run away from the magma!")); } else if(cwt.at->wall == waChasm) { if(markOrb2(itOrbAether)) return false; + if(in_gravity_zone(cwt.at) && passable(cwt.at, cwt.at, P_ISPLAYER)) return false; if(checkonly) return true; flags |= AF_FALL; addMessage(XLAT("The floor has collapsed! RUN!")); @@ -6314,6 +6414,8 @@ bool hasSafeOrb(cell *c) { void checkmove() { + dynamicval gs(gravity_state, gravity_state); + #if CAP_INV if(inv::on) inv::compute(); #endif @@ -6825,8 +6927,10 @@ bool collectItem(cell *c2, bool telekinesis) { else if(it == itOrbSpeed) playSound(c2, "pickup-speed"); else if(it == itRevolver) playSound(c2, "pickup-key"); else playSound(c2, "pickup-orb"); + int oc = orbcharges(it); + if(markOrb(itOrbBrown)) oc = oc * 6 / 5; if(!items[it]) items[it]++; - items[it] += orbcharges(it); + items[it] += oc; } else if(c2->item == itOrbLife) { playSound(c2, "pickup-orb"); // TODO summon @@ -6987,7 +7091,16 @@ bool collectItem(cell *c2, bool telekinesis) { eItem dummy = c2->item; conformal::findhistory.emplace_back(c2, dummy); #endif - c2->item = itNone; + + if(c2->item == itBombEgg && c2->land == laMinefield) { + c2->landparam |= 2; + c2->landparam &= ~1; + } + + if(items[itOrbChoice] && itemclass(c2->item) == IC_ORB) + items[itOrbChoice] = 0; + else + c2->item = itNone; } // if(c2->land == laHive) // c2->heat = 1; @@ -7584,9 +7697,19 @@ bool movepcto(int d, int subdir, bool checkonly) { return false; } + gravity_state = gsNormal; + if(d >= 0) { cell *c2 = cwt.at->move(d); bool goodTortoise = c2->monst == moTortoise && tortoise::seek() && !tortoise::diff(tortoise::getb(c2)) && !c2->item; + + if(items[itOrbGravity]) { + if(c2->monst && !should_switchplace(cwt.at, c2)) + gravity_state = get_static_gravity(cwt.at); + else + gravity_state = get_move_gravity(cwt.at, c2); + if(gravity_state) markOrb(itOrbGravity); + } if(againstRose(cwt.at, c2) && !scentResistant()) { if(checkonly) return false; @@ -7676,7 +7799,7 @@ bool movepcto(int d, int subdir, bool checkonly) { } if(againstCurrent(c2, cwt.at) && !markOrb(itOrbWater)) { - if(markOrb(itOrbFish) || markOrb(itOrbAether)) goto escape; + if(markOrb(itOrbFish) || markOrb(itOrbAether) || gravity_state) goto escape; if(!checkonly) addMessage(XLAT("You cannot go against the current!")); return false; @@ -7934,8 +8057,9 @@ bool movepcto(int d, int subdir, bool checkonly) { addMessage(XLAT("You would get hurt!", c2->wall)); else if(cwt.at->wall == waTower && snakelevel(c2) == 0) addMessage(XLAT("You would get hurt!", c2->wall)); - else if(cellEdgeUnstable(cwt.at) && cellEdgeUnstable(c2)) + else if(cellEdgeUnstable(cwt.at) && cellEdgeUnstable(c2)) { addMessage(XLAT("Gravity does not allow this!")); + } else if(c2->wall == waChasm && c2->land == laDual) addMessage(XLAT("You cannot move there!")); else { @@ -8084,6 +8208,10 @@ bool movepcto(int d, int subdir, bool checkonly) { } } else { + if(items[itOrbGravity]) { + gravity_state = get_static_gravity(cwt.at); + if(gravity_state) markOrb(itOrbGravity); + } lastmovetype = lmSkip; lastmove = NULL; if(checkNeedMove(checkonly, false)) return false; @@ -8098,6 +8226,9 @@ bool movepcto(int d, int subdir, bool checkonly) { dropGreenStone(cwt.at); if(cellUnstable(cwt.at) && !markOrb(itOrbAether)) doesFallSound(cwt.at); + + if(last_gravity_state && !gravity_state) + playerMoveEffects(cwt.at, cwt.at); } invisfish = false; @@ -8110,6 +8241,7 @@ bool movepcto(int d, int subdir, bool checkonly) { if(invisfish) invismove = true, markOrb(itOrbFish); } + last_gravity_state = gravity_state; if(multi::players == 1) monstersTurn(); save_memory(); diff --git a/graph.cpp b/graph.cpp index 15c315a0..5091dc5c 100644 --- a/graph.cpp +++ b/graph.cpp @@ -524,7 +524,7 @@ transmatrix otherbodyparts(const transmatrix& V, color_t col, eMonster who, doub #define VAHEAD mmscale(V, geom3::AHEAD) #define VFISH V -#define VBIRD mmscale(V, geom3::BIRD + .05 * sintick(1000, (int) (size_t(where))/1000.)) +#define VBIRD ((where && bird_disruption(where)) ? V : mmscale(V, geom3::BIRD + .05 * sintick(1000, (int) (size_t(where))/1000.))) #define VGHOST mmscale(V, geom3::GHOST) #define VSLIMEEYE mscale(V, geom3::FLATEYE) @@ -781,7 +781,7 @@ bool drawItemType(eItem it, cell *c, const transmatrix& V, int icol, int pticks, isFriendOrb(it) ? shPeaceRing : isUtilityOrb(it) ? shGearRing : isDirectionalOrb(it) ? shSpearRing : - it == itOrb37 ? shHeptaRing : + among(it, itOrb37, itOrbGravity) ? shHeptaRing : shRing; queuepolyat(V * spinptick(1500), sh, col, prio); } @@ -3626,6 +3626,76 @@ int colorhash(color_t i) { return (i * 0x471211 + i*i*0x124159 + i*i*i*0x982165) & 0xFFFFFF; } +void draw_gravity_particles(cell *c, const transmatrix V) { + int u = (int)(size_t)(c); + u = ((u * 137) + (u % 1000) * 51) % 1000; + int tt = ticks + u; + ld r0 = (tt % 900) / 1100.; + ld r1 = (tt % 900 + 200) / 1100.; + + const color_t grav_normal_color = 0x808080FF; + const color_t antigrav_color = 0xF04040FF; + const color_t levitate_color = 0x40F040FF; + + ld lev = 2; + + if(spatial_graphics) { + + switch(gravity_state) { + case gsNormal: + for(int i=0; i<6; i++) { + transmatrix T = V * spin(i*degree*60) * xpush(crossf/3); + queueline(mmscale(T, 1 + (1-r0) * (lev-1)) * C0, mmscale(T, 1 + (1-r1) * (lev - 1)) * C0, grav_normal_color); + } + break; + + case gsAnti: + for(int i=0; i<6; i++) { + transmatrix T = V * spin(i*degree*60) * xpush(crossf/3); + queueline(mmscale(T, 1 + r0 * (lev-1)) * C0, mmscale(T, 1 + r1 * (lev-1)) * C0, antigrav_color); + } + break; + + case gsLevitation: + for(int i=0; i<6; i++) { + transmatrix T0 = V * spin(i*degree*60 + tt/60. * degree) * xpush(crossf/3); + transmatrix T1 = V * spin(i*degree*60 + (tt/60. + 30) * degree) * xpush(crossf/3); + queueline(mmscale(T0, (lev+1)/2) * C0, mmscale(T1, (lev+1)/2) * C0, levitate_color); + } + break; + } + } + + else { + switch(gravity_state) { + case gsNormal: + for(int i=0; i<6; i++) { + transmatrix T0 = V * spin(i*degree*60) * xpush(crossf/3 * (1-r0)); + transmatrix T1 = V * spin(i*degree*60) * xpush(crossf/3 * (1-r1)); + queueline(T0 * C0, T1 * C0, grav_normal_color); + } + break; + + case gsAnti: + for(int i=0; i<6; i++) { + transmatrix T0 = V * spin(i*degree*60) * xpush(crossf/3 * r0); + transmatrix T1 = V * spin(i*degree*60) * xpush(crossf/3 * r1); + queueline(T0 * C0, T1 * C0, antigrav_color); + } + break; + + case gsLevitation: + for(int i=0; i<6; i++) { + transmatrix T0 = V * spin(i*degree*60 + tt/60. * degree) * xpush(crossf/3); + transmatrix T1 = V * spin(i*degree*60 + (tt/60. + 30) * degree) * xpush(crossf/3); + queueline(T0 * C0, T1 * C0, levitate_color); + } + break; + } + } + } + + void drawcell(cell *c, transmatrix V, int spinv, bool mirrored) { cells_drawn++; @@ -4886,7 +4956,10 @@ void drawcell(cell *c, transmatrix V, int spinv, bool mirrored) { queuepoly(V * ddspin(c, i) * xpush(cellgfxdist(c, i)/2), shWindArrow, 0x8080FF80); } } - + + if(items[itOrbGravity] && c->cpdist <= 5) + draw_gravity_particles(c, V); + if(c->land == laWhirlwind) { whirlwind::calcdirs(c); @@ -5527,6 +5600,7 @@ void drawthemap() { lmouseover = mouseover; bool useRangedOrb = (!(vid.shifttarget & 1) && haveRangedOrb() && lmouseover && lmouseover->cpdist > 1) || (keystate[SDLK_RSHIFT] | keystate[SDLK_LSHIFT]); if(!useRangedOrb && !(cmode & sm::MAP) && !(cmode & sm::DRAW) && DEFAULTCONTROL && !mouseout()) { + dynamicval gs(gravity_state, gravity_state); void calcMousedest(); calcMousedest(); cellwalker cw = cwt; bool f = flipplayer; @@ -5886,7 +5960,7 @@ void drawscreen() { } } - if((minefieldNearby || tmines) && !items[itOrbAether] && darken == 0 && normal) { + if((minefieldNearby || tmines) && !items[itOrbAether] && !last_gravity_state && darken == 0 && normal) { string s; if(tmines > 7) tmines = 7; color_t col = minecolors[tmines]; diff --git a/hyper.h b/hyper.h index da07660e..72d3614a 100644 --- a/hyper.h +++ b/hyper.h @@ -1437,6 +1437,7 @@ void setGLProjection(color_t col = backcolor); bool passable(cell *w, cell *from, flagtype flags); +bool anti_alchemy(cell *w, cell *from); bool isElemental(eLand l); int coastval(cell *c, eLand base); int getHauntedDepth(cell *c); @@ -2082,6 +2083,9 @@ bool mayExplodeMine(cell *c, eMonster who); void explosion(cell *c, int power, int central); void explodeBarrel(cell *c); +enum eGravity { gsNormal, gsLevitation, gsAnti }; +extern eGravity gravity_state, last_gravity_state; + int gravityLevel(cell *c); int gravityLevelDiff(cell *c, cell *f); void fullcenter(); @@ -4715,6 +4719,8 @@ namespace racing { inline bool subscreen_split(reaction_t for_each_subscreen) { return false; } #endif +bool in_gravity_zone(cell *c); +bool normal_gravity_at(cell *c); } diff --git a/inventory.cpp b/inventory.cpp index 63512416..3757c894 100644 --- a/inventory.cpp +++ b/inventory.cpp @@ -335,6 +335,10 @@ namespace hr { namespace inv { gainOrbs(itSwitch, itOrbPhasing); gainOrbs(itMagnet, itOrbMagnetism); gainOrbs(itRuins, itOrbSlaying); + + gainOrbs(itWest, itOrbGravity); + gainOrbs(itVarTreasure, itOrbChoice); + gainOrbs(itOrbBrown, itBrownian); #if CAP_DAILY daily::gifts(); diff --git a/orbgen.cpp b/orbgen.cpp index b538a884..a8aa1520 100644 --- a/orbgen.cpp +++ b/orbgen.cpp @@ -117,6 +117,9 @@ const vector orbinfos = { {orbgenflags::S_NATIVE, laSwitch, 2000, 3000, itOrbPhasing}, {orbgenflags::S_NATIVE, laMagnetic, 2000, 3000, itOrbMagnetism}, {orbgenflags::S_NATIVE, laRuins, 1200, 2500, itOrbSlaying}, + {orbgenflags::S_NATIVE, laWestWall, 2000, 4200, itOrbGravity}, + {orbgenflags::S_NATIVE, laVariant, 900, 4200, itOrbChoice}, + {orbgenflags::S_NATIVE, laBrownian, 900, 4200, itOrbBrown}, {orbgenflags::S_NATIVE, laWhirlpool, 0, 2000, itOrbWater}, // needs to be last }; diff --git a/orbs.cpp b/orbs.cpp index 38566748..553ed4b8 100644 --- a/orbs.cpp +++ b/orbs.cpp @@ -72,6 +72,7 @@ bool reduceOrbPower(eItem it, int cap) { items[it] -= multi::activePlayers(); if(isHaunted(cwt.at->land)) survivalist = false; if(items[it] < 0) items[it] = 0; + if(items[it] > cap && markOrb(itOrbBrown)) cap = cap * 6 / 5; if(items[it] > cap && timerghost) items[it] = cap; if(items[it] == 0 && it == itOrbLove) princess::bringBack(); @@ -143,6 +144,9 @@ void reduceOrbPowers() { reduceOrbPower(itOrbLava, 80); reduceOrbPower(itOrbMorph, 80); reduceOrbPower(itOrbSlaying, 120); + reduceOrbPower(itOrbGravity, 120); + reduceOrbPower(itOrbChoice, 120); + reduceOrbPower(itOrbBrown, 120); reduceOrbPower(itOrbSide1, 120); reduceOrbPower(itOrbSide2, 120); @@ -1342,6 +1346,12 @@ int orbcharges(eItem it) { case itOrbInvis: case itOrbAether: return 30; + case itOrbGravity: + return 45; + case itOrbChoice: + return 60; + case itOrbBrown: + return 50; case itOrbWinter: // "pickup-winter" return inv::on ? 45 : 30; break;