diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build diff --git a/Makefile b/Makefile index 36e1a28..5e6ce20 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,9 @@ PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) OBJS = pinetab2_framework.o game.o SPRITES = $(patsubst $(PROJECT_ROOT)sprites/%.xcf,%,$(wildcard $(PROJECT_ROOT)sprites/*.xcf)) +NAVMESHES = $(patsubst $(PROJECT_ROOT)navmesh/%.tmx,%,$(wildcard $(PROJECT_ROOT)navmesh/*.tmx)) +BUILD_MODE := debug ifeq ($(BUILD_MODE),debug) CFLAGS += -g else ifeq ($(BUILD_MODE),run) @@ -16,12 +18,13 @@ else $(error Build mode $(BUILD_MODE) not supported by this Makefile) endif -CXX := aarch64-linux-gnu-$(CXX) -CC := aarch64-linux-gnu-$(CC) +CXX := aarch64-linux-gnu-g++ +CC := aarch64-linux-gnu-gcc OBJCOPY := aarch64-linux-gnu-objcopy -O elf64-littleaarch64 CFLAGS += -I/usr/include OBJS += $(patsubst %,sprite_%.o,$(SPRITES)) +OBJS += $(patsubst %,navmesh_%.o,$(NAVMESHES)) all: pinetab2_framework $(patsubst %,%.png,$(SPRITES)) @@ -29,18 +32,21 @@ pinetab2_framework: $(OBJS) $(CXX) $(LDFLAGS) -o $@ $^ $(EXTRA_CMDS) -%.o: $(PROJECT_ROOT)%.cpp +%.o: $(PROJECT_ROOT)%.cpp $(CXX) -c $(CFLAGS) $(CXXFLAGS) $(CPPFLAGS) -o $@ $< -%.o: $(PROJECT_ROOT)%.c +%.o: $(PROJECT_ROOT)%.c $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $< +%.o: %.c + $(CXX) -c $(CFLAGS) $(CXXFLAGS) $(CPPFLAGS) -o $@ $< -iquote "$(PROJECT_ROOT)" + clean: rm -fr pinetab2_framework $(OBJS) $(EXTRA_CLEAN) sprite_%.o: $(PROJECT_ROOT)sprites/%.xcf gimp -in -b '(let ((image (car (gimp-xcf-load 0 "$<" "$(notdir $<)")))) ;\ - (gimp-image-scale image 1280 800) ;\ + ;(gimp-image-scale image 1280 800) ;\ (gimp-image-rotate image ROTATE-90) ;\ (let ((layer (car (gimp-image-merge-visible-layers image CLIP-TO-IMAGE)))) ;\ (plug-in-colors-channel-mixer RUN-NONINTERACTIVE image layer 0 0 0 1 0 1 0 1 0 0) ; swap red and blue channels \ @@ -51,9 +57,12 @@ sprite_%.o: $(PROJECT_ROOT)sprites/%.xcf rm $(patsubst %.o,%.raw,$@) @# symbols are _binary_sprite_%_raw_start / _end / _size +navmesh_%.c: $(PROJECT_ROOT)navmesh/%.tmx $(PROJECT_ROOT)compile_navmesh.py + python3 "$(PROJECT_ROOT)compile_navmesh.py" "$<" "$@" "$(basename $@)" + %.png: $(PROJECT_ROOT)sprites/%.xcf gimp -in -b '(let ((image (car (gimp-xcf-load 0 "$<" "$(notdir $<)")))) ;\ - (gimp-image-scale image 1280 800) ;\ + ;(gimp-image-scale image 1280 800) ;\ (let ((layer (car (gimp-image-merge-visible-layers image CLIP-TO-IMAGE)))) ;\ (file-png-save 1 image layer "$@" "$@" 0 9 0 0 0 0 0) ;\ ) ;\ diff --git a/compile_navmesh.py b/compile_navmesh.py new file mode 100644 index 0000000..2091722 --- /dev/null +++ b/compile_navmesh.py @@ -0,0 +1,110 @@ +#!/usr/bin/python3 +import lxml.etree, math, sys + +_, inpath, outpath, basename = sys.argv + +tree = lxml.etree.parse(inpath) + +tris = [] +edge2tri = {} + +for polygon in tree.findall("objectgroup[@name='navmesh']/object/polygon"): + basex = int(polygon.getparent().attrib["x"]) + basey = int(polygon.getparent().attrib["y"]) + points=[] + for point in polygon.attrib["points"].split(" "): + x,y=point.split(",") + x,y=int(x)+basex,int(y)+basey + points.append((x,y)) + #print(points) + + ax,ay=points[0] + bx,by=points[1] + cx,cy=points[2] + abx,aby=bx-ax,by-ay + acx,acy=cx-ax,cy-ay + cross = abx*acy-acx*aby + # positive for clockwise winding order. Example clockwise winding order: (0,0), (1,0), (0,1) + if cross < 0: + points = points[0],points[2],points[1] + bx,by,cx,cy=cx,cy,bx,by + cross = -cross + + assert len(points)==3, "triangles only" + tri_id = len(tris) + tris.append(points) + + for edge in [(points[0],points[1]), (points[1],points[2]), (points[2],points[0])]: + edge_id = tuple(sorted(edge)) + if edge_id not in edge2tri: edge2tri[edge_id] = [] + edge2tri[edge_id].append(tri_id) + assert len(edge2tri[edge_id]) <= 2, "more than 2 triangles share edge "+str(edge_id) + +pathfind_edges = [[] for _ in range(len(tris))] +for edgetris in edge2tri.values(): + assert len(edgetris) in (1,2) + if len(edgetris) == 1: continue + t1,t2 = edgetris + pathfind_edges[t1].append(t2) + pathfind_edges[t2].append(t1) + +pathfind_matrix = [[255 for _1 in range(len(tris))] for _2 in range(len(tris))] +# pathfind_matrix[source][dest] is how to get to dest from source + +for dest in range(len(tris)): + # Find paths to tri by breadth-first search. We don't take the size of the triangle into account yet. + openset = [dest] + while len(openset)>0: + cur, openset = openset[0], openset[1:] + for adjacent in pathfind_edges[cur]: + if adjacent != dest and pathfind_matrix[adjacent][dest] == 255: + pathfind_matrix[adjacent][dest] = cur + openset.append(adjacent) + + + +out = open(outpath,"w") + +out.write("#include \"compiled_structures.h\"\n") +out.write(f"static const struct navmesh_tri {basename}_triangles[{len(tris)}] = {{\n"); +for tri_id,points in enumerate(tris): + out.write("\t{\n\t\t{\n"); + for a,b,c in [(points[0],points[1],points[2]), (points[1],points[2],points[0]), (points[2],points[0],points[1])]: + (ax,ay),(bx,by),(cx,cy) = a,b,c + # edge a-b and c is the other point + # Line equation Px+Qy+R=0 from https://math.stackexchange.com/questions/422602/convert-two-points-to-line-eq-ax-by-c-0 + P,Q,R=ay-by,bx-ax,ax*by-bx*ay + # Line equation >0 for points inside the triangle. + # Normalize so that P^2+Q^2=1 so that the equation result tells us the distance. This can be used to find the closest triangle to a point. + norm=1/math.sqrt(P*P+Q*Q) + P *= norm + Q *= norm + R *= norm + + edge_id = tuple(sorted((a,b))) + tris_on_edge = edge2tri[edge_id] + assert len(tris_on_edge) in (1,2) + assert tri_id in tris_on_edge + if len(tris_on_edge) == 2: + other_tri_id = [x for x in tris_on_edge if x!=tri_id][0] + else: + other_tri_id = -1 + + centx, centy = (ax+bx)/2, (ay+by)/2 + + out.write(f"\t\t\t{{{P},{Q},{R},{other_tri_id},{{{int(centx)},{int(centy)}}}}},\n") + + + + out.write("\t\t},\n") + out.write("\t\t{" + ",".join(f"{{{x},{y}}}" for x,y in points) + "},\n") + out.write("\t},\n"); +out.write("};\n") +out.write(f"static const unsigned char {basename}_pathfind[{len(tris)*len(tris)}] = {{\n") +for dest in range(len(tris)): + out.write("\t") + for source in range(len(tris)): + out.write(str(pathfind_matrix[source][dest])+",") + out.write("\n") +out.write("};\n") +out.write(f"extern const struct navmesh {basename} = {{{len(tris)}, {basename}_triangles, {basename}_pathfind}};\n") diff --git a/compiled_structures.h b/compiled_structures.h new file mode 100644 index 0000000..d772d2c --- /dev/null +++ b/compiled_structures.h @@ -0,0 +1,27 @@ +#ifndef COMPILED_STRUCTURES_H_ +#define COMPILED_STRUCTURES_H_ + + +struct navmesh_point { + int x, y; +}; +struct navmesh_tri_edge { + float a,b,c; // line equation a*x + b*y + c > 0 if point is in triangle, and value gives distance from line. + int other_tri; // -1 if this edge is a border of navmesh, else triangle it connects to + struct navmesh_point center; // intermediate point for use in routing. could be improved later. +}; +struct navmesh_tri { + struct navmesh_tri_edge edges[3]; + struct navmesh_point points[3]; + // edges[0]: points[0]-points[1] + // edges[1]: points[1]-points[2] + // edges[2]: points[2]-points[0] +}; +struct navmesh { + int num_tris; + const struct navmesh_tri *tris; + const unsigned char *pathfindgrid; // [dest*num_tris + source] gives index of next tri. Placeholder value is used when dest==source. +}; + + +#endif /* COMPILED_STRUCTURES_H_ */ diff --git a/engine.h b/engine.h index aee7d51..20c9413 100644 --- a/engine.h +++ b/engine.h @@ -1,4 +1,4 @@ -#define MAX_GAME_OBJECTS 300 +#define MAX_GAME_OBJECTS 100 extern struct object { int id; // 0 if slot unused int x; @@ -8,15 +8,51 @@ extern struct object { const char *pixels; } objects[MAX_GAME_OBJECTS]; +#define MAX_GAME_SCRIPTS 100 +typedef void (*script_wake_fn)(struct script *scr, int wakeupMode, int arg1, int arg2, int arg3, int arg4); +#define SCRIPT_HEADER \ + int id /* 0 if slot unused */; \ + int wakeupMode; \ + script_wake_fn wakeupFn; \ + int wakeupArg1; /* for SCRIPT_WAKEUP_OTHER_SCRIPT */ +extern struct script { + SCRIPT_HEADER + + int vars[100]; + +} scripts[MAX_GAME_SCRIPTS]; + +struct script_player_walk { + SCRIPT_HEADER + int targetX; + int targetY; + int targetNavmeshTri; + int currentNavmeshTri; +}; + +#define SCRIPT_WAKEUP_VIDEO_FRAME 1 +#define SCRIPT_WAKEUP_OTHER_SCRIPT 2 +#define SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED 3 // Delivered to script that registers for SCRIPT_WAKEUP_OTHER_SCRIPT if that script is interrupted instead of completing. + + // engine void transition_scene(int scene); // Immediately stops all event processing for current scene, which is unloaded before this function returns void scene_add_clickrect(int id, int x, int y, int width, int height); void scene_add_object(int id, int x, int y, int width, int height, const char *pixels); +void scene_set_navmesh(struct navmesh *navmesh); +struct object *find_object_by_id(int id); +struct script *scene_add_script(int id, bool interrupt_existing); +struct script *scene_get_script(int id); +bool deliver_script_wakeup(int wakeupMode, int wakeupArg1, int wakeupType, int arg1, int arg2, int arg3, int arg4); // deliver to scripts with given wakeupMode and wakeupArg1. Returns true if scene changed. // game-specific constants #define SCENE_LOBBY 1 #define SCENE_MANAGERS_OFFICE 2 +// The player ID and walk script are globally relevant +#define OBJID_PLAYER 4 +#define OBJID_PLAYER_WALK_SCRIPT 5 + // game.cpp void scene_setup(int scene); void onclick(int curscene, int objid); diff --git a/game.cpp b/game.cpp index bfd6d33..076b2c3 100644 --- a/game.cpp +++ b/game.cpp @@ -6,19 +6,28 @@ extern const char _binary_sprite_lobby_raw_start[]; extern const char _binary_sprite_managers_office_raw_start[]; +extern const char _binary_sprite_stickman_raw_start[]; + +extern struct navmesh navmesh_lobby; + #define OBJID_BACKGROUND 1 #define OBJID_DOOR_TO_MANAGERS_OFFICE_FROM_LOBBY 2 #define OBJID_DOOR_TO_LOBBY_FROM_MANAGERS_OFFICE 3 +// #define OBJID_PLAYER 4 +// #define SCRIPTID_PLAYER_WALK 5 void scene_setup(int scene) { switch(scene) { case SCENE_LOBBY: scene_add_object(OBJID_BACKGROUND, 0, 0, BGWIDTH, BGHEIGHT, _binary_sprite_lobby_raw_start); scene_add_object(OBJID_DOOR_TO_MANAGERS_OFFICE_FROM_LOBBY, 273, 313, 76, 128, nullptr); + scene_add_object(OBJID_PLAYER, 424, 575, 51, 111, _binary_sprite_stickman_raw_start); + scene_set_navmesh(&navmesh_lobby); break; case SCENE_MANAGERS_OFFICE: scene_add_object(OBJID_BACKGROUND, 0, 0, BGWIDTH, BGHEIGHT, _binary_sprite_managers_office_raw_start); scene_add_object(OBJID_DOOR_TO_LOBBY_FROM_MANAGERS_OFFICE, 273, 313, 76, 128, nullptr); + scene_add_object(OBJID_PLAYER, 424, 575, 51, 111, _binary_sprite_stickman_raw_start); break; } } @@ -41,3 +50,4 @@ void onclick(int curscene, int objid) { break; } } + diff --git a/navmesh/lobby.tmx b/navmesh/lobby.tmx new file mode 100644 index 0000000..a1682c4 --- /dev/null +++ b/navmesh/lobby.tmx @@ -0,0 +1,68 @@ + + + + +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pinetab2_framework.cpp b/pinetab2_framework.cpp index 472f081..ce369e8 100644 --- a/pinetab2_framework.cpp +++ b/pinetab2_framework.cpp @@ -17,14 +17,22 @@ #include #include #include "engine.h" +#include "compiled_structures.h" #define PLAYFIELD_WIDTH 1280 #define PLAYFIELD_HEIGHT 800 - static int touchscreen_fd; +static struct navmesh null_navmesh = {0}; +struct navmesh *cur_navmesh = &null_navmesh; +struct object objects[MAX_GAME_OBJECTS]; +struct script scripts[MAX_GAME_SCRIPTS]; +bool need_rerender = false; + + + #define MAX_MT_SLOTS 16 static struct mtslot { int x, y, touch, lasttouch; @@ -131,7 +139,8 @@ struct sprite { static uint32_t *curfb; // used during render cycle static void blit(int x, int y, const struct sprite *spr) { uint32_t *inbase = spr->pixels; - printf("blit %dx%d at %d,%d\n", spr->width, spr->height, x, y); + //printf("blit %dx%d at %d,%d\n", spr->width, spr->height, x, y); + y = PLAYFIELD_HEIGHT - spr->height - y; if(y >= PLAYFIELD_HEIGHT || x >= PLAYFIELD_WIDTH || y <= -spr->height || x <= -spr->width) return; if(spr->height > PLAYFIELD_HEIGHT) error(1, 0, "sprite bigger than playfield not supported"); // would need to clip top and bottom simultaneously - not implemented for now @@ -153,8 +162,9 @@ static void blit(int x, int y, const struct sprite *spr) { } } -struct object objects[MAX_GAME_OBJECTS]; -bool need_rerender = false; +void scene_set_navmesh(struct navmesh *navmesh) { + cur_navmesh = navmesh; +} static uint32_t scene_generation_count = 0; static int curscene; @@ -162,6 +172,8 @@ void transition_scene(int scene) { scene_generation_count++; curscene = scene; memset(objects, 0, sizeof objects); + memset(scripts, 0, sizeof scripts); // script state is local to scene, so no cancel notification + cur_navmesh = &null_navmesh; scene_setup(scene); } @@ -182,6 +194,106 @@ void scene_add_object(int id, int x, int y, int width, int height, const char *p error(1, 0, "too many game objects"); } +struct object *find_object_by_id(int id) { + for(int i = 0; i < MAX_GAME_OBJECTS; i++) { + if(objects[i].id == id) + return &objects[i]; + } + return nullptr; +} +struct script *scene_add_script(int id, bool interrupt_existing) { + if(interrupt_existing && id != OBJID_PLAYER_WALK_SCRIPT) error(1, 0, "scene_add_script: interrupt not implemented"); + for(int i = 0; i < MAX_GAME_SCRIPTS; i++) { + if(scripts[i].id == id && interrupt_existing) { + // interrupt existing script? + // player walk script is safe to trivially cancel, at least + scripts[i].id = 0; + } + if(scripts[i].id == 0) { + memset(&scripts[i], 0, sizeof(scripts[i])); + scripts[i].id = id; + return &scripts[i]; + } + } + error(1, 0, "too many game scripts"); + __builtin_unreachable(); +} + +static void closest_point_inside_tri(int *x, int *y, const struct navmesh_tri *tri) { + double fx = *x, fy = *y; + int edgeHitMask = 0; + + // Moving a point outside of one half-plane can move it into another half-plane, so check again. + // If this happens, we must be in a corner region. + // Repeated normal-vector moves don't necessarily bring us to the corner, but we can just hardcode the corner. + for(int j = 0; j < 2; j++) { + for(int i = 0; i < 3; i++) { + double edgeval = fx*tri->edges[i].a + fy*tri->edges[i].b + tri->edges[i].c; + if(edgeval <= 0) { + // edge.a,b is normalized so that moving 1 unit of a and 1 unit of b changes edgeval by 1. + fx -= tri->edges[i].a*edgeval; + fy -= tri->edges[i].b*edgeval; + edgeHitMask |= 1 << i; + } + } + } + + switch(edgeHitMask) { + case 3: // hit edge 0-1 and 1-2 + *x = tri->points[1].x; + *y = tri->points[1].y; + return; + case 5: // hit edge 0-1 and 2-0 + *x = tri->points[0].x; + *y = tri->points[0].y; + return; + case 6: // hit edge 1-2 and 2-0 + *x = tri->points[2].x; + *y = tri->points[2].y; + return; + } + + // may be slightly outside of triangle due to rounding errors, but good enough for us + *x = (int)(fx + 0.5); + *y = (int)(fy + 0.5); +} + +static double point_to_point_dist(int x, int y, const struct navmesh_point *pt) { + x -= pt->x; + y -= pt->y; + return sqrt(x*x + y*y); +} + +// Returns distance from triangle, 0 if inside. +static double is_point_in_tri(int x, int y, const struct navmesh_tri *tri) { + double edgedist[3]; // positive if outside + for(int edge = 0; edge < 3; edge++) + edgedist[edge] = -(x*tri->edges[edge].a + y*tri->edges[edge].b + tri->edges[edge].c); + if(edgedist[0] < 0 && edgedist[1] < 0 && edgedist[2] < 0) return 0; // inside + if(edgedist[0] >= 0 && edgedist[1] >= 0) return point_to_point_dist(x, y, &tri->points[0]); + if(edgedist[1] >= 0 && edgedist[2] >= 0) return point_to_point_dist(x, y, &tri->points[1]); + if(edgedist[2] >= 0 && edgedist[0] >= 0) return point_to_point_dist(x, y, &tri->points[2]); + if(edgedist[0] >= 0) return edgedist[0]; + if(edgedist[1] >= 0) return edgedist[1]; + if(edgedist[2] >= 0) return edgedist[2]; + return 0; // branch should be unreachable; maybe within epsilon of triangle border? +} +static int find_navmesh_tri(int x, int y, int tolerance) { + int best_tri = -1; + double best_dist = tolerance; + for(int i = 0; i < cur_navmesh->num_tris; i++) { + double dist = is_point_in_tri(x, y, &cur_navmesh->tris[i]); + if(dist == 0) + return i; + if(dist < best_dist) { + best_dist = dist; + best_tri = i; + } + } + return best_tri; +} + +static void update_player_walk_script_on_frame(struct script *scr, int wakeupMode, int, int, int, int); void handle_tap(int x, int y) { uint32_t gencount = scene_generation_count; for(int i = 0; i < MAX_GAME_OBJECTS; i++) { @@ -190,8 +302,108 @@ void handle_tap(int x, int y) { onclick(curscene, objects[i].id); } } + + struct script_player_walk *player_walk_script = (struct script_player_walk *)scene_add_script(OBJID_PLAYER_WALK_SCRIPT, true); + + struct object *player = find_object_by_id(OBJID_PLAYER); + if(player) { + int destTri = find_navmesh_tri(x, y, 50); + if(destTri != -1) { + int targetX = x, targetY = y; + closest_point_inside_tri(&targetX, &targetY, &cur_navmesh->tris[destTri]); + + player_walk_script->targetX = targetX; + player_walk_script->targetY = targetY; + player_walk_script->targetNavmeshTri = destTri; + player_walk_script->wakeupMode = SCRIPT_WAKEUP_VIDEO_FRAME; + player_walk_script->wakeupFn = update_player_walk_script_on_frame; + + int sourceTri = find_navmesh_tri(player->x+player->width*2, player->y+player->height, 1000000); + player_walk_script->currentNavmeshTri = sourceTri; + printf("set course from triangle %d to %d\n", sourceTri, destTri); + } else { + player_walk_script->id = 0; + } + } else { + player_walk_script->id = 0; + } } +static int move_towards(int cur, int target, int maxrate) { + if(target > cur+maxrate) return cur+maxrate; + if(target < cur-maxrate) return cur-maxrate; + return target; +} + +static void update_player_walk_script_on_frame(struct script *scr, int wakeupMode, int, int, int, int) { + struct script_player_walk *player_walk_script = (struct script_player_walk *)scr; + if(!player_walk_script) return; + struct object *player = find_object_by_id(OBJID_PLAYER); + if(!player) { + player_walk_script->id = 0; + return; + } + + if(player_walk_script->currentNavmeshTri == -1) { + // something weird happened. just teleport player and end movement. + player->x = player_walk_script->targetX - player->width/2; + player->y = player_walk_script->targetY - player->height; + player_walk_script->id = 0; + need_rerender = true; + } else { + int x = player->x+player->width/2; + int y = player->y+player->height; + int nmtri = player_walk_script->currentNavmeshTri; + int nextX; + int nextY; + int nextTri = -1; + if(nmtri == player_walk_script->targetNavmeshTri) { + nextX = player_walk_script->targetX; + nextY = player_walk_script->targetY; + } else { + nextTri = cur_navmesh->pathfindgrid[player_walk_script->targetNavmeshTri * cur_navmesh->num_tris + nmtri]; + nextX = -1; + for(int edge = 0; edge < 3; edge++) { + if(cur_navmesh->tris[nmtri].edges[edge].other_tri == nextTri) { + nextX = cur_navmesh->tris[nmtri].edges[edge].center.x; + nextY = cur_navmesh->tris[nmtri].edges[edge].center.y; + goto center_found; + } + } + // shouldn't happen + player_walk_script->id = 0; + return; + center_found:; + } + if(nextX == x && nextY == y) { + printf("in triangle %d, next triangle %d\n", nmtri, nextTri); + if(x == player_walk_script->targetX && y == player_walk_script->targetY) { + player_walk_script->id = 0; + } else { + player_walk_script->currentNavmeshTri = nextTri; // even if -1 + } + } else { + x = move_towards(x, nextX, 5); + y = move_towards(y, nextY, 2); + player->x = x - player->width/2; + player->y = y - player->height; + } + need_rerender = true; + } +} + +bool deliver_script_wakeup(int wakeupMode, int wakeupArg1, int wakeupType, int arg1, int arg2, int arg3, int arg4) { + uint32_t gen = scene_generation_count; + for(int i = 0; i < MAX_GAME_SCRIPTS; i++) { + if(scripts[i].id && scripts[i].wakeupMode == wakeupMode && scripts[i].wakeupArg1 == wakeupArg1) { + scripts[i].wakeupFn(&scripts[i], wakeupType, arg1, arg2, arg3, arg4); + if(scene_generation_count != gen) return true; // scene changed - abort early + } + } + return false; +} + + int main(int argc, char **argv) { if(getuid() != 0) { @@ -296,7 +508,7 @@ int main(int argc, char **argv) { uint64_t dummy; // don't care about expiration count if(read(timerfd, &dummy, sizeof dummy) < 0) error(1, errno, "read (timerfd)"); current_animframe++; - printf("animation frame %u\n", (unsigned int)current_animframe); + //printf("animation frame %u\n", (unsigned int)current_animframe); need_rerender = true; } if(polls[2].revents & POLLIN) { @@ -319,20 +531,18 @@ int main(int argc, char **argv) { } } + if(!awaiting_vblank) { + // return value ignored + // What is this really doing? It's giving scripts the opportunity to trigger a video frame. + // But if no script actually does, this can run over and over many times in the same video frame. + // It's not saying a video frame happened, it's saying one can happen. + deliver_script_wakeup(SCRIPT_WAKEUP_VIDEO_FRAME, 0, SCRIPT_WAKEUP_VIDEO_FRAME, 0, 0, 0, 0); + } + if(need_rerender && !awaiting_vblank) { cur_buffer = 1-cur_buffer; // for testing, just keep writing into the same buffer curfb = fbs[cur_buffer]; - - //printf("%d %d\n", sizeof basement_pixels, 1280*800*4); - /*struct sprite bgsprite = { - .width = 1280, - .height = 800, - .pixels = (uint32_t*)_binary_sprite_lobby_raw_start - }; - memset(curfb, 0, 1280*800*4); - blit(mtslots[0].y-640, mtslots[0].x-400, &bgsprite);*/ - for(int i = 0; i < MAX_GAME_OBJECTS; i++) { if(objects[i].id != 0 && objects[i].pixels) { struct sprite spr = {.width = objects[i].width, .height = objects[i].height, .pixels = (uint32_t*)objects[i].pixels}; diff --git a/sprites/lobby.xcf b/sprites/lobby.xcf index 5eb4c52..b22aded 100644 Binary files a/sprites/lobby.xcf and b/sprites/lobby.xcf differ diff --git a/sprites/managers_office.xcf b/sprites/managers_office.xcf index f668ddd..10d09ac 100644 Binary files a/sprites/managers_office.xcf and b/sprites/managers_office.xcf differ diff --git a/sprites/stickman.xcf b/sprites/stickman.xcf new file mode 100644 index 0000000..eb961ed Binary files /dev/null and b/sprites/stickman.xcf differ