1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2025-05-14 13:14:08 +00:00
2025-04-29 17:57:28 +02:00

404 lines
11 KiB
C++

#include "../../rogueviz/rogueviz.h"
/** \brief Hyperbolic platformer.
The implementation of the platformer from https://twitter.com/ZenoRogue/status/1467233150380089345
To play, load platformer.lev (e.g. `-load platformer.lev`)
Or create a new map by running with options:
`-noscr -canvas B -geo dbin -platformer`
Keys:
up/left/right -- move the guy
c -- clear the current room (buggy, does not clear parts of the adjacent rooms)
g -- generate the current room (buggy)
m -- see the map (toggle)
p -- pause (toggle)
z -- screenshot menu
v -- HyperRogue settings
q -- quit
s -- save to platformer.lev
1 -- place a small block under the mouse
2 -- place a big block under the mouse
3 -- delete the block under the mouse
Have fun!
*/
#include "globals.cpp"
#include "classes.cpp"
#include "geometry.cpp"
#include "entity.cpp"
#include "man.cpp"
#include "room.cpp"
#include "render.cpp"
#include "portals.cpp"
#include "powers.cpp"
#include "save.cpp"
namespace rogue_unlike {
static double gtime = 0;
bool last_mkey = false;
extern int mousepx, mousepy;
int lev = 0;
void floodfill(int x, int y, int src, int tgt) {
dynamicval<int> d(lev, lev+1);
println(hlog, lev, ": ", tie(x, y));
if(x < 0 || y < 0 || x >= room_x || y >= room_y) return;
if(y > room_y - b_margin && tgt) return;
if(current_room->block_at[y][x] != src) return;
current_room->place_block_full(x, y, tgt);
floodfill(x+1, y, src, tgt);
floodfill(x-1, y, src, tgt);
floodfill(x, y+1, src, tgt);
floodfill(x, y-1, src, tgt);
}
void floodfill_pick(int x, int y) {
if(x < 0 || y < 0 || x >= room_x || y >= room_y) return;
auto cur = current_room->block_at[y][x];
if(cur != 8*sel) floodfill(x, y, cur, 8 * sel);
else floodfill(x, y, 8 * sel, 0);
}
bool last_keystate[KEYSTATES], cur_keystate[KEYSTATES];
bool keyheld(int id) { return cur_keystate[id]; }
bool keywasheld(int id) { return last_keystate[id]; }
bool keypressed(int id) { return cur_keystate[id] && !last_keystate[id]; }
void update_keystate() {
const Uint8 *keystate = SDL12_GetKeyState(NULL);
for(int i=0; i<KEYSTATES; i++) last_keystate[i] = cur_keystate[i], cur_keystate[i] = keystate[i];
}
void editmap_frame() {
if(keyheld('1'))
current_room->place_block_full(mousepx / block_x, mousepy / block_y, 8*sel);
if(keyheld('2'))
current_room->place_block_full(mousepx / block_x, mousepy / block_y, 8*sel+4);
if(keyheld('3'))
current_room->place_block_full(mousepx / block_x, mousepy / block_y, 0);
if(keypressed('4')) pushScreen([] {
dialog::init();
dialog::addTitle("what to add", 0x4040C0, 150);
for(int i=1; i<qwall; i++) {
dialog::addItem(walls[i].name, 'a'+i);
dialog::add_action([i] { sel = i; popScreen(); });
}
dialog::display();
});
if(keypressed('f')) floodfill_pick(mousepx / block_x, mousepy / block_y);
if(keypressed('t')) { m.where_x = mousepx; m.where_y = mousepy; m.vel_x = 0; m.vel_y = 0; }
}
void playing_frame() {
m.act();
auto& ents = current_room->entities;
for(auto& e: ents) e->act();
auto mb = ents.begin();
for(auto& e: ents) if(!e->destroyed) *(mb++) = std::move(e);
ents.resize(mb - ents.begin());
auto& nonh = non_hyperbolic;
if(one_room) return;
if(m.where_x < l_margin_at) {
m.where_x += actual_screen_x;
switch_to_adjacent_room(2);
m.clearg();
}
if(m.where_x > r_margin_at) {
m.where_x -= actual_screen_x;
switch_to_adjacent_room(nonh ? 0 : 4);
m.clearg();
}
if(m.where_y < t_margin_at && !nonh) {
m.where_y = (m.where_y - t_margin_at) * 2 + b_margin_at;
m.where_x -= l_margin_at;
m.where_x = 2 * m.where_x;
if(m.where_x > actual_screen_x) {
m.where_x -= actual_screen_x;
switch_to_adjacent_room(0);
}
else
switch_to_adjacent_room(1);
m.where_x += l_margin_at;
m.vel_x *= 2; m.vel_y *= 2;
m.clearg();
}
if(m.where_y > b_margin_at && !nonh) {
m.where_x -= l_margin_at;
m.where_y -= b_margin_at;
m.where_y /= 2;
m.where_y += t_margin_at;
if(is_right(current_room))
m.where_x += actual_screen_x;
switch_to_adjacent_room(3);
m.where_x /= 2;
m.where_x += l_margin_at;
m.vel_x /= 2; m.vel_y /= 2;
m.clearg();
}
}
void sync_map() {
for(auto& p: rooms)
if(&p.second == current_room)
centerover = p.first;
View = cspin90(1, 0);
// if(cmode == mode::playing) View = View * inverse(parabolic13_at(to_hyper(m.where_x, m.where_y)));
if(cmode == mode::playing) {
hyperpoint p = to_hyper(m.where_x, m.where_y);
transmatrix T = iso_inverse(parabolic13_at(deparabolic13(p)));
View = View * T;
}
}
void switch_mapmode_to(mapmode m) {
cmapmode = m;
switch(cmapmode) {
case mapmode::standard:
nomap = true;
break;
case mapmode::poincare:
case mapmode::klein:
sync_map();
pconf.scale = 0.95;
pconf.alpha = cmapmode == mapmode::poincare ? 1 : 0;
pmodel = mdDisk;
nomap = false;
break;
}
}
void render_the_map() {
switch(cmapmode) {
case mapmode::standard:
emptyscreen();
render_room(current_room);
draw_room();
drawmessages();
nomsg = false;
if(1) {
ld& scale = scrm.T[0][0];
mousepx = (mousex - current_display->xcenter) * 2 / scale / current_display->radius + screen_x/2;
mousepy = (mousey - current_display->ycenter) * 2 / scale / current_display->radius + screen_y/2;
}
dialog::add_key_action('v', [] { cmode = mode::menu; });
break;
case mapmode::poincare:
case mapmode::klein:
gamescreen();
if(!mouseout() && mouseover && rooms.count(mouseover)) {
current_room = &rooms[mouseover];
auto h = inverse_shift(ggmatrix(current_room->where), mouseh);
tie(mousepx, mousepy) = from_hyper(h);
}
if(cmode == mode::editmap) {
getcstat = '-';
dialog::add_key_action('-', [] { if(!mouseover) return; current_room = &rooms[mouseover]; switch_mapmode_to(mapmode::standard); });
}
break;
}
}
void run() {
// clearMessages();
dialog::init();
switch(cmode) {
case mode::editmap:
case mode::playing:
case mode::paused:
if(cmode == mode::playing) sync_map();
render_the_map();
if(cmode == mode::editmap) dialog::add_key_action('p', [] { println(hlog, "p pressed"); switch_mapmode_to(mapmode::poincare); });
if(cmode == mode::editmap) mouseovers = format("coordinates: %d %d (%.2lf)", mousepx, mousepy, double(get_scale_at(mousepy)));
break;
case mode::inventory:
render_the_map();
draw_inventory();
dialog::add_key_action('v', [] { cmode = mode::menu; });
break;
case mode::menu:
nomap = true;
emptyscreen();
dialog::init();
dialog::addTitle("Fountains of Alchemy", 0x4040C0, 150);
dialog::addItem("return to game", 'v');
dialog::add_action([] { cmode = mode::playing; });
dialog::addItem("inventory", 'i');
dialog::add_action([] { cmode = mode::inventory; });
dialog::addItem("map editor", 'e');
dialog::add_action([] { cmode = mode::editmap; });
dialog::addItem("screenshot", 'z');
dialog::add_action([] { pushScreen(shot::menu); });
dialog::addItem("save to editmap.ru", 's');
dialog::add_key_action('s', [] {
save_map("rogueviz/ru/editmap.ru");
});
dialog::display();
break;
}
keyhandler = [] (int sym, int uni) {
if(among(cmode, mode::paused, mode::editmap) && cmapmode != mapmode::standard)
handlePanning(sym, uni);
dialog::handleNavigation(sym, uni);
};
}
void add_platf_hooks();
void set_sval() {
ld s_min = 10, s_max = 1200;
for(int it=0; it<100; it++) {
mscale = sqrt(s_min * s_max);
hyperpoint atop = deparabolic13(to_hyper(0, t_margin_at));
if(atop[0] < -log(2)/2) s_max = mscale;
else s_min = mscale;
}
}
void enable() {
stop_game();
set_sval();
init_scales();
hyperpoint aleft = deparabolic13(to_hyper(l_margin_at, yctr));
hyperpoint aright = deparabolic13(to_hyper(r_margin_at, yctr));
vid.binary_width = abs(aright[1] - aleft[1]) / log(2);
start_game();
cgi.prepare_shapes();
current_room = get_room_at(cwt.at);
prepare_tinf();
add_platf_hooks();
load_map("rogueviz/ru/map.ru");
switch_mapmode_to(mapmode::standard);
}
void add_platf_hooks() {
rogueviz::rv_hook(hooks_prestats, 90, [=] {
if(nomap)
draw_room();
else
render_room(current_room);
return true;
});
rogueviz::rv_hook(hooks_resetGL, 90, [=] {
for(auto& [w, r]: rooms) if(r.rbuf) { delete r.rbuf; r.rbuf = nullptr; }
});
rogueviz::rv_hook(shmup::hooks_turn, 90, [=] (int d) {
gtime += d;
while(gtime > 1000. / game_fps) {
gtime -= 1000. / game_fps;
gframeid++;
update_keystate();
switch(cmode) {
case mode::editmap:
editmap_frame();
break;
case mode::playing:
playing_frame();
break;
case mode::paused:
data d;
handle_powers(d);
break;
default: ;
}
}
return true;
});
rogueviz::rv_hook(hooks_drawcell, 90, draw_room_on_map);
rogueviz::rv_hook(mapstream::hooks_savemap, 100, [] (hstream& f) {
f.write<int>(67);
for(auto& p: rooms) {
f.write(mapstream::cellids[p.first]);
for(int y=0; y<room_y; y++)
for(int x=0; x<room_x; x++)
f.write(p.second.block_at[y][x]);
f.write<int>(0);
}
f.write<int>(-1);
f.write(mapstream::cellids[current_room->where]);
f.write(m.where_x);
f.write(m.where_y);
f.write(m.vel_x);
f.write(m.vel_y);
});
pushScreen(run);
}
auto chk = arg::add3("-ru", enable)
+ addHook(mapstream::hooks_loadmap, 100, [] (hstream& f, int id) {
if(id == 67) {
println(hlog, "loading platformer");
while(true) {
int i = f.get<int>();
if(i == -1) break;
auto r = get_room_at(mapstream::cellbyid[i]);
for(int y=0; y<room_y; y++)
for(int x=0; x<room_x; x++)
f.read(r->block_at[y][x]);
r->clearfov();
r->infile = true;
f.get<int>();
}
int id = f.get<int>();
current_room = get_room_at(mapstream::cellbyid[id]);
f.read(m.where_x);
f.read(m.where_y);
f.read(m.vel_x);
f.read(m.vel_y);
add_platf_hooks();
println(hlog, "done");
set_sval();
cgi.prepare_shapes();
prepare_tinf();
init_scales();
cmode = mode::playing;
println(hlog, "set mode to playing");
}
});
}