namespace rogue_unlike { man m; bbox entity::get_pixel_bbox_at(xy p) { bbox b; double d = get_scale_at(p.y); double man_x = siz().x; double man_y = siz().y; b.minx = p.x - man_x * d / 2; b.maxx = p.x + man_x * d / 2 + 1; b.miny = p.y - man_y * d / 2; b.maxy = p.y + man_y * d / 2 + 1; return b; } bool entity::visible(room *r) { auto bb = get_intersect(pixel_to_block(get_pixel_bbox()), room_bb); for(int y = bb.miny; y < bb.maxy; y++) for(int x = bb.minx; x < bb.maxx; x++) if(r->fov[y][x]) return true; return false; } data entity::get_dat() { data dat; dat.d = get_scale(); dat.modv = 60. / game_fps; dat.moda = dat.modv * dat.modv; dat.dx = 0; return dat; } void entity::apply_grav() { if(non_hyperbolic) return apply_portal_grav(); auto dat = get_dat(); vel.y += dat.d * grav() * dat.moda * 16/9.; } void entity::apply_vel() { int bx0 = floor(where.x / block_x); int by0 = floor(where.y / block_y); where += vel; int bx1 = floor(where.x / block_x); int by1 = floor(where.y / block_y); if(non_hyperbolic) { auto& loc = all_locations[by0][bx0]; if(bx1 > bx0) { auto& loc1 = loc.get(0); where.x += block_x * (loc1.x - (loc.x + 1)); where.y += block_y * (loc1.y - loc.y); } if(bx1 < bx0) { auto& loc1 = loc.get(2); where.x += block_x * (loc1.x - (loc.x - 1)); where.y += block_x * (loc1.y - loc.y); } if(by1 < by0) { auto& loc1 = loc.get(1); where.x += block_x * (loc1.x - loc.x); where.y += block_x * (loc1.y - (loc.y - 1)); } if(by1 > by0) { auto& loc1 = loc.get(3); where.x += block_x * (loc1.x - loc.x); where.y += block_x * (loc1.y - (loc.y + 1)); } } // ld test_x, test_y; // tie(test_x, test_y) = from_hyper(h_at); /*println(hlog, tie(where_x, where_y), " TO ", h_at, " TO ", tie(test_x, test_y)); exit(1); */ if(true || !non_hyperbolic) { hyperpoint h_at = to_hyper(where); hyperpoint h_was = to_hyper(where - vel); hyperpoint h_willbe = rgpushxto0(h_at) * MirrorX * MirrorY * gpushxto0(h_at) * h_was; xy next = from_hyper(h_willbe); vel = next - where; } } void entity::apply_walls() { int loopcount = 0; again: loopcount++; auto obb = pixel_to_block(get_pixel_bbox()); auto nbb = pixel_to_block(get_pixel_bbox_at(where + vel)); auto jbb = join(obb, nbb); flagtype blocking = (vel.y < 0 || fallthru) ? W_BLOCK : (W_BLOCK | W_PLATFORM); auto pain_effect = [&] { if(!hurt_by_spikes()) return false; reduce_hp(10); vel.x = -vel.x; vel.y = -vel.y; apply_grav(); return true; }; for(int x = obb.minx; x < obb.maxx; x++) for(int y = obb.maxy; y < jbb.maxy; y++) { eWall b = current_room->at(x, y); if(walls[b].flags & blocking) { if(walls[b].flags & W_BOUNCY) { vel.y = -vel.y; on_bounce = true; goto again; } on_floor = true; if(walls[b].flags & W_FROZEN) on_ice = true; vel.y /= 2; if(abs(vel.y) < 1e-6) vel.y = 0; if(freezing()) { if(b == wWater) current_room->replace_block(x, y, wFrozen); else if(b != wFrozen) hit_wall(); } if(pixel_to_block(get_pixel_bbox_at(where + vel)).maxy <= y) where.y += vel.y; if(walls[b].flags & W_STABLE) is_stable = true; goto again; } if((walls[b].flags & W_PAIN) && pain_effect()) goto again; } for(int x = obb.minx; x < obb.maxx; x++) for(int y = jbb.miny; y < obb.miny; y++) { eWall b = current_room->at(x, y); if(walls[b].flags & W_BLOCK) { vel.y /= 2; if(abs(vel.y) < 1e-6) vel.y = 0; if(pixel_to_block(get_pixel_bbox_at(where + vel)).miny > y) where.y += vel.y; goto again; } if((walls[b].flags & W_PAIN) && pain_effect()) goto again; } if(!fallthru) for(int x = nbb.minx; x < nbb.maxx; x++) for(int y = jbb.maxy-1; y < jbb.maxy; y++) { eWall b = current_room->at(x, y); if(walls[b].flags & W_STAIRCASE) { on_floor = true; if(vel.y > 0) { vel.y = 0; goto again; } } } for(int x = obb.maxx; x < jbb.maxx; x++) for(int y = jbb.miny; y < jbb.maxy; y++) { eWall b = current_room->at(x, y); if(walls[b].flags & W_BLOCK) { if(freezing()) { hit_wall(); } vel.x = (vel.x - max(vel.y, 0)/10) / 2; wallhug = true; goto again; } if((walls[b].flags & W_PAIN) && pain_effect()) goto again; } for(int x = jbb.minx; x < obb.minx; x++) for(int y = jbb.miny; y < jbb.maxy; y++) { eWall b = current_room->at(x, y); if(walls[b].flags & W_BLOCK) { if(freezing()) { hit_wall(); } vel.x = (vel.x + max(vel.y, 0)/10) / 2; wallhug = true; goto again; } if((walls[b].flags & W_PAIN) && pain_effect()) goto again; } if(loopcount < 100) for(auto& e: current_room->entities) if(auto p = e->as_platform()) { auto opw = p->location_at(gframeid-1); auto npw = p->location_at(gframeid); xy screen_ctr(xctr, yctr); auto rmow = (where - opw) / get_scale_at(opw.y) + screen_ctr; auto rmnw = (where + vel - npw) / get_scale_at(npw.y) + screen_ctr; auto rvel = rmnw - rmow; auto eb = get_pixel_bbox_at(rmnw); auto pb = p->get_pixel_bbox_at(screen_ctr); if(intersect(pb, eb)) { zero_vel = (rmow - screen_ctr) * get_scale_at(npw.y) + npw - where; bool reset = false; if(intersect(pb, get_pixel_bbox_at(rmow))) { /* should not happen */ } else if(!intersect(pb, get_pixel_bbox_at(rmow + xy(rvel.x, 0))) && rvel.y > 0) { on_floor = true; rvel.y /= 2; if(abs(rvel.y) < 1e-6) rvel.y = 0; reset = true; } else if(!intersect(pb, get_pixel_bbox_at(rmow + xy(rvel.x, 0))) && rvel.y < 0) { rvel.y /= 2; if(abs(rvel.y) < 1e-6) rvel.y = 0; reset = true; } else { rvel.x = -rvel.x; reset = true; } vel = (rmow + rvel - screen_ctr) * get_scale_at(npw.y) + npw - where; if(reset) goto again; } } } bool entity::stay_on_screen() { bool res = false; if(where.x < l_margin_at && vel.x < 0) vel.x = -vel.x, res = true; if(where.x > r_margin_at && vel.x > 0) vel.x = -vel.x, res = true; if(where.y < t_margin_at && vel.y < 0) vel.y = -vel.y, res = true; if(where.y > b_margin_at && vel.y > 0) vel.y = -vel.y, res = true; return res; } void entity::kino() { on_floor = false; on_ice = false; wallhug = false; on_bounce = false; is_stable = false; zero_vel = xy(0, 0); // ld modv = 60. / game_fps; apply_grav(); apply_walls(); apply_vel(); apply_grav(); gwhere += gvel; ld delta = 1/60.; auto z = (where - gwhere) * (gvel - vel); if(z.x + z.y < 0) delta *= 2; ld ndelta = 1-delta; gvel = ndelta * gvel + delta * (where - gwhere); } void missile::act() { kino(); if(where.x > screen_x || where.x < 0 || where.y < 0 || where.y > screen_y) destroyed = true; } void npc_or_trader::act() { kino(); if(gframeid > m.last_action + 300 && intersect(extend_all(get_pixel_bbox(), get_scale()*12), m.get_pixel_bbox()) && talk_on != m.last_action) { talk_on = m.last_action = gframeid; cmode = mode::menu; pushScreen([&] { cmode = mode::playing; popScreen(); }); pushScreen([&] { dialog::init(name, color() >> 8); dialog::addHelp(parse_markup(text)); dialog::addBreak(100); dialog::addBack(); dialog::display(); }); } } extern int gold_id; string shopitem::glyph() { if(bought) return powers[gold_id].get_glyph(); else return item::glyph(); } color_t shopitem::color() { if(bought) return powers[gold_id].get_color(); else return item::color(); } void trader::act() { bool any_purchases = false; for(auto& e: current_room->entities) if(auto si = e->as_shopitem()) if(!si->existing) any_purchases = true; if(any_purchases) { walls[wShopDoor].glyph = '+'; walls[wShopDoor].flags = W_BLOCK | W_BLOCKBIRD; } else { walls[wShopDoor].glyph = '\''; walls[wShopDoor].flags = W_TRANS; } if(any_purchases) talk_on = m.last_action; npc_or_trader::act(); } void boar::act() { stay_on_screen(); kino(); if(intersect(get_pixel_bbox(), m.get_pixel_bbox())) { int s = where.x < m.where.x ? -1 : 1; if(m.reduce_hp(15)) addMessage("The wild boar gores you!"); auto dat = get_dat(); auto mdat = m.get_dat(); if(m.on_floor) m.vel.x = mdat.d * mdat.modv * -s * 1.5, m.vel.y = -mdat.d * mdat.modv * 2; if(on_floor) vel.x = dat.d * dat.modv * s * 1.5; } if(on_floor) { auto dat = get_dat(); if(vel.x > 0) vel.x = max(vel.x - dat.d * dat.moda * 0.05, 0); if(vel.x < 0) vel.x = min(vel.x + dat.d * dat.moda * 0.05, 0); if(gframeid > invinc_end) { if(intersect(extend(get_pixel_bbox(), 60 * dat.d, 0, 0, 0), m.get_pixel_bbox())) vel.x -= dat.d * dat.moda * 0.2; if(intersect(extend(get_pixel_bbox(), 0, 60 * dat.d, 0, 0), m.get_pixel_bbox())) vel.x += dat.d * dat.moda * 0.2; } } } void enemy::attacked(int dmg) { current_target = this; if(reduce_hp(dmg)) { if(!existing) addMessage("You kill the " + get_name() + "."); else addMessage("You hit the " + get_name() + "."); } } void boar::attacked(int dmg) { enemy::attacked(dmg); auto dat = get_dat(); int s = where.x < m.where.x ? -1 : 1; if(on_floor) vel.x = dat.d * dat.modv * s * 2, vel.y = -dat.d * dat.modv * 2.5; } void ghost::act() { hyperpoint g = to_hyper(where); hyperpoint h = to_hyper(m.where); ld d = hdist(g, h); ld angle = gframeid < invinc_end ? M_PI : d > 0.5 ? 90._deg : d > 0.05 ? 80._deg : 5._deg; ld gv = d > 0.04 && gframeid > invinc_end ? 0.2 : 0.1; if(flipped) angle = -angle; hyperpoint g1 = rgpushxto0(g) * rspintox(gpushxto0(g) * h) * spin(angle) * xpush0(gv / game_fps); vel = from_hyper(g1) - where; if(stay_on_screen()) flipped = !flipped; apply_vel(); if(intersect(get_pixel_bbox(), m.get_pixel_bbox()) && gframeid > invinc_end) { invinc_end = gframeid + 200; if(m.reduce_hp(20)) addMessage("The ghost passes through you!"); } } void snake::act() { stay_on_screen(); kino(); if(abs(vel.x) < 1e-6) { auto dat = get_dat(); vel.x = zero_vel.x + dat.d * dat.modv * dir; dir = -dir; } if(intersect(get_pixel_bbox(), m.get_pixel_bbox())) { if(m.reduce_hp(25)) addMessage("The snake bites you!"); } } void snake::attacked(int dmg) { enemy::attacked(dmg); if(where.x < m.where.x) vel.x = -abs(vel.x); if(where.x > m.where.x) vel.x = +abs(vel.x); } void hint::act() { bool cur = intersect(get_pixel_bbox(), m.get_pixel_bbox()); if(gframeid < 300) cur = 0; if(cur && !state) { addMessage(parse_markup(hint_text)); } state = cur; } xy ferris_platform::location_at(ld t) { return from_hyper(rgpushxto0(to_hyper(ctr)) * xspinpush0(t / game_fps + shift, radius)); } xy pendulum_platform::location_at(ld t) { auto h1 = to_hyper(a); auto h2 = to_hyper(b); auto d = hdist(h1, h2); auto x = (1 - cos(t / game_fps * TAU / period)) / 2 * d; return from_hyper(rgpushxto0(h1) * rspintox(gpushxto0(h1) * h2) * xpush0(x)); } void moving_platform::draw() { double d = get_scale(); for(int w=-1; w<=1; w++) { ld minx = where.x + siz().x * d * (w - 0.5) / 3; ld miny = where.y - siz().y * d / 2; ld maxx = where.x + siz().x * d * (w + 0.5) / 3; ld maxy = where.y + siz().y * d / 2; asciiletter(minx, miny, maxx, maxy, "#", 0xFFFFFFFF); } } void moving_platform::act() { where = location_at(gframeid); } void entity::apply_walls_reflect() { int loopcount = 0; again: loopcount++; auto obb = pixel_to_block(get_pixel_bbox()); auto nbb = pixel_to_block(get_pixel_bbox_at(where + vel)); auto jbb = join(obb, nbb); flagtype blocking = (W_BLOCK | W_BLOCKBIRD); if(loopcount >= 100) return; for(int x = obb.minx; x < obb.maxx; x++) for(int y = obb.maxy; y < jbb.maxy; y++) { eWall b = current_room->at(x, y); if(walls[b].flags & blocking) { vel.y = -vel.y; goto again; } } for(int x = obb.minx; x < obb.maxx; x++) for(int y = jbb.miny; y < obb.miny; y++) { eWall b = current_room->at(x, y); if(walls[b].flags & blocking) { vel.y = -vel.y; goto again; } } for(int x = nbb.minx; x < nbb.maxx; x++) for(int y = jbb.miny; y < jbb.maxy; y++) { eWall b = current_room->at(x, y); if(walls[b].flags & blocking) { vel.x = -vel.x; goto again; } } for(int x = obb.maxx; x < jbb.maxx; x++) for(int y = jbb.miny; y < jbb.maxy; y++) { eWall b = current_room->at(x, y); if(walls[b].flags & blocking) { vel.x = -vel.x; goto again; } } } void kestrel::act() { stay_on_screen(); apply_walls_reflect(); apply_vel(); if(intersect(get_pixel_bbox(), m.get_pixel_bbox())) { if(m.reduce_hp(15)) addMessage("The kestrel claws you!"); } } void gridbug::act() { if(intersect(get_pixel_bbox(), m.get_pixel_bbox())) { if(m.reduce_hp(15)) addMessage("The grid bug zaps you!"); } if(gframeid < next_move || !visible(current_room) || gframeid < invinc_end) return; auto gridbox = pixel_to_block(get_pixel_bbox()); array, room_y> times; for(int y=0; yentities) if(&*e != this && e->existing) { auto obox = pixel_to_block(e->get_pixel_bbox()); for(int x=obox.minx; x>> q; auto visit = [&] (int x, int y, ld t) { q.push({-t, {x, y}}); }; auto manbox = pixel_to_block(m.get_pixel_bbox()); for(int x=manbox.minx; x memt) continue; memt = t; auto move_to = [&] (int x1, int y1) { if(x1 < 0 || y1 < 0 || x1 >= room_x || y1 >= room_y) return; auto b = current_room->at(x1, y1); flagtype blocking = (W_BLOCK | W_BLOCKBIRD); if(walls[b].flags & blocking) return; ld d = hdist(to_hyper(block_x*(x+.5), block_y*(y+.5)), to_hyper(block_x*(x1+.5), block_y*(y+1.5))) * 10; if(x1 == origx && y1 == origy && rest > t+d) { rest = t+d; resx = x; resy = y; res_move_t = d; } visit(x1, y1, t+d); }; move_to(x+1, y); move_to(x-1, y); move_to(x, y+1); move_to(x, y-1); } next_move = gframeid + game_fps * res_move_t; where = xy(block_x * (resx + .5), block_y * (resy + .5)); } void bat::act() { if(gframeid >= next_change && gframeid > invinc_end + 300) { next_change = gframeid + 300 + rand() % 300; int angle = rand() % 360; auto dat = get_dat(); ld v = dat.d * dat.modv; vel.x = v * cos(angle); vel.y = v * sin(angle); } stay_on_screen(); apply_walls_reflect(); apply_vel(); if(intersect(get_pixel_bbox(), m.get_pixel_bbox())) { if(m.reduce_hp(15)) addMessage("The bat bites you!"); } } void kestrel::attacked(int dmg) { enemy::attacked(dmg); if(where.x < m.where.x) vel.x = -abs(vel.x); if(where.x > m.where.x) vel.x = +abs(vel.x); } void bat::attacked(int dmg) { enemy::attacked(dmg); if(where.x < m.where.x) vel.x = -abs(vel.x); if(where.x > m.where.x) vel.x = +abs(vel.x); if(where.y < m.where.y) vel.y = -abs(vel.y); if(where.y > m.where.y) vel.y = +abs(vel.y); } }