2025-02-17 16:03:14 +00:00
# include <libdrm/drm.h>
# include <libdrm/drm_fourcc.h>
# include <errno.h>
# include <error.h>
# include <stdio.h>
# include <unistd.h>
# include <stdint.h>
# include <stdlib.h>
# include <fcntl.h>
# include <string.h>
# include <sys/ioctl.h>
# include <sys/mman.h>
# include <sys/random.h>
# include <linux/input.h>
# include <math.h>
# include <sys/timerfd.h>
# include <sys/poll.h>
2025-02-17 19:11:44 +00:00
# include <inttypes.h>
# include "engine.h"
2025-02-18 01:04:17 +00:00
# include "compiled_structures.h"
2025-02-18 22:11:20 +00:00
# include "objids.h"
2025-02-17 16:03:14 +00:00
2025-02-17 19:11:44 +00:00
# define PLAYFIELD_WIDTH 1280
# define PLAYFIELD_HEIGHT 800
2025-02-17 16:03:14 +00:00
static int touchscreen_fd ;
2025-02-18 01:04:17 +00:00
static struct navmesh null_navmesh = { 0 } ;
2025-02-18 21:03:37 +00:00
struct scene scenes [ MAX_STACKED_SCENES ] ;
int scene_depth = 0 ;
2025-02-18 01:04:17 +00:00
bool need_rerender = false ;
2025-02-19 02:51:11 +00:00
struct savefile savefile ;
2025-02-18 01:04:17 +00:00
2025-02-17 16:03:14 +00:00
# define MAX_MT_SLOTS 16
static struct mtslot {
2025-02-17 19:11:44 +00:00
int x , y , touch , lasttouch ;
2025-02-17 16:03:14 +00:00
int lastx , lasty ;
} mtslots [ MAX_MT_SLOTS ] = { 0 } ;
static struct mtslot * curslot = & mtslots [ 0 ] ; // TODO: query the current value of ABS_MT_SLOT
static void poll_touchscreen ( ) {
struct input_event buf [ 64 ] ;
int nread ;
retry_read :
nread = read ( touchscreen_fd , buf , sizeof buf ) ;
if ( nread < 0 ) {
if ( errno = = EINTR )
goto retry_read ;
if ( errno = = EAGAIN )
return ;
error ( 1 , errno , " read touchscreen " ) ;
}
if ( nread % sizeof ( struct input_event ) )
error ( 1 , 0 , " odd size read from touchscreen " ) ;
for ( int i = 0 ; i < nread ; i + = sizeof ( struct input_event ) ) {
struct input_event * evt = ( struct input_event * ) ( ( char * ) buf + i ) ;
//printf("type=%d code=%d value=%d\n", evt->type, evt->code, evt->value);
if ( evt - > type = = EV_SYN & & evt - > code = = SYN_DROPPED ) error ( 0 , 0 , " got SYN_DROPPED " ) ; // TODO: resynchronize
if ( evt - > type = = EV_SYN & & evt - > code = = SYN_REPORT ) {
for ( int j = 0 ; j < MAX_MT_SLOTS ; j + + ) {
struct mtslot * s = & mtslots [ j ] ;
if ( s - > touch ) {
//printf("slot %d x=%d y=%d\n", j, s->x, s->y);
2025-02-17 19:11:44 +00:00
/*uint32_t col;
2025-02-17 16:03:14 +00:00
switch ( j & 3 ) {
case 0 : col = 0xFFFFFFFF ; break ;
case 1 : col = 0xFF0000FF ; break ;
case 2 : col = 0x00FF00FF ; break ;
case 3 : col = 0x0080FFFF ; break ;
}
int segs = s - > lastx < 0 ? 1 : ( int ) ( 0.9 + 2 * sqrt ( ( s - > lastx - s - > x ) * ( s - > lastx - s - > x ) + ( s - > lasty - s - > y ) * ( s - > lasty - s - > y ) ) ) ;
if ( segs < 1 ) segs = 1 ;
for ( int f = 0 ; f < = segs ; f + + ) {
int _x , _y ;
if ( s - > lastx ! = - 1 ) {
_x = ( s - > lastx + ( s - > x - s - > lastx ) * f / segs ) ;
_y = ( s - > lasty + ( s - > y - s - > lasty ) * f / segs ) ;
} else {
_x = s - > x ;
_y = s - > y ;
}
for ( int dy = - 5 ; dy < = 5 ; dy + + ) {
for ( int dx = - 5 ; dx < = 5 ; dx + + ) {
if ( dx * dx + dy * dy < = 5 * 5 ) {
int x = dx + _x , y = dy + _y ;
if ( x > = 0 & & y > = 0 & & x < 800 & & y < 1280 ) {
* ( uint32_t * ) ( fb + ( y * 800 + x ) ) = col ;
}
}
}
}
2025-02-17 19:11:44 +00:00
} */
if ( ! s - > lasttouch ) {
2025-02-19 00:55:56 +00:00
top_scene . handle_tap_fn ( & top_scene , s - > y , 799 - s - > x ) ;
2025-02-17 16:03:14 +00:00
}
}
2025-02-17 19:11:44 +00:00
s - > lasttouch = s - > touch ;
s - > lastx = s - > x ;
s - > lasty = s - > y ;
2025-02-17 16:03:14 +00:00
}
}
if ( evt - > type = = EV_ABS & & evt - > code = = ABS_MT_SLOT ) {
if ( evt - > value > = 0 & & evt - > value < MAX_MT_SLOTS ) curslot = & mtslots [ evt - > value ] ;
else curslot = NULL ;
}
if ( evt - > type = = EV_ABS & & evt - > code = = ABS_MT_POSITION_X ) {
if ( curslot ) curslot - > x = evt - > value ;
}
if ( evt - > type = = EV_ABS & & evt - > code = = ABS_MT_POSITION_Y ) {
if ( curslot ) curslot - > y = evt - > value ;
}
if ( evt - > type = = EV_ABS & & evt - > code = = ABS_MT_TRACKING_ID ) {
if ( curslot ) {
if ( ! curslot - > touch ) {
curslot - > lastx = curslot - > lasty = - 1 ;
}
curslot - > touch = evt - > value ! = - 1 ;
}
}
}
}
2025-02-17 19:11:44 +00:00
// Rendering is dumb, double-buffered with page flip, always redrawing the whole frame.
// It could be improved.
uint32_t current_animframe ;
2025-02-19 00:55:56 +00:00
uint32_t * curfb ; // used during render cycle
void blit ( int x , int y , int width , int height , uint32_t * pixels ) {
uint32_t * inbase = pixels ;
2025-02-18 01:04:17 +00:00
//printf("blit %dx%d at %d,%d\n", spr->width, spr->height, x, y);
2025-02-19 00:55:56 +00:00
y = PLAYFIELD_HEIGHT - height - y ;
if ( y > = PLAYFIELD_HEIGHT | | x > = PLAYFIELD_WIDTH | | y < = - height | | x < = - width ) return ;
if ( height > PLAYFIELD_HEIGHT ) error ( 1 , 0 , " sprite bigger than playfield not supported " ) ; // would need to clip top and bottom simultaneously - not implemented for now
2025-02-17 19:11:44 +00:00
uint32_t * outbase = curfb + x * PLAYFIELD_HEIGHT + y ;
2025-02-19 00:55:56 +00:00
for ( int dx = 0 ; dx < width ; dx + + , inbase + = height , outbase + = PLAYFIELD_HEIGHT , x + + ) {
2025-02-17 19:11:44 +00:00
if ( x < 0 ) continue ;
if ( x > = PLAYFIELD_WIDTH ) break ;
if ( y < 0 ) {
// TODO: loop inversion would make sense
int offset = - y ;
// must have at least one pixel to copy
2025-02-19 00:55:56 +00:00
memcpy ( outbase + offset , inbase + offset , ( height - offset ) * 4 ) ;
2025-02-17 19:11:44 +00:00
} else {
int ncopy = PLAYFIELD_HEIGHT - y ;
2025-02-19 00:55:56 +00:00
if ( ncopy > height ) ncopy = height ;
2025-02-17 19:11:44 +00:00
memcpy ( outbase , inbase , ncopy * 4 ) ;
}
}
}
2025-02-19 00:55:56 +00:00
void fillrect ( int x1 , int y1 , int width , int height , uint32_t pixel ) {
int x2 = x1 + width ;
int y2 = y1 + height ;
if ( x1 < 0 ) x1 = 0 ;
if ( y1 < 0 ) y1 = 0 ;
if ( x2 > = PLAYFIELD_WIDTH ) x2 = PLAYFIELD_WIDTH ;
if ( y2 > = PLAYFIELD_HEIGHT ) y2 = PLAYFIELD_HEIGHT ;
y1 = PLAYFIELD_HEIGHT - 1 - y1 ;
y2 = PLAYFIELD_HEIGHT - 1 - y2 ;
for ( int x = x1 ; x < x2 ; x + + ) {
uint32_t * outbase = curfb + x * PLAYFIELD_HEIGHT + y2 ;
for ( int y = y2 ; y < y1 ; y + + ) {
* outbase + + = pixel ;
}
}
}
static void scene_clear_and_setup ( int scene , int fromscene , scene_setup_fn setup_fn ) {
2025-02-19 02:09:45 +00:00
struct scene * me = & top_scene ; // setup_fn might push additional scenes such as textboxes
memset ( me , 0 , sizeof ( * me ) ) ;
me - > id = scene ;
me - > navmesh = & null_navmesh ;
me - > handle_tap_fn = standard_handle_tap ;
me - > render_fn = standard_scene_render ;
me - > use_standard_inventory = true ;
setup_fn ( me , scene , fromscene ) ;
if ( me - > use_standard_inventory ) {
create_standard_inventory ( me ) ;
}
2025-02-17 16:03:14 +00:00
2025-02-19 00:55:56 +00:00
need_rerender = true ;
2025-02-18 21:03:37 +00:00
}
2025-02-19 00:55:56 +00:00
void push_scene ( int scene , scene_setup_fn setup_fn ) {
2025-02-18 21:03:37 +00:00
scene_depth + + ;
if ( scene_depth > MAX_STACKED_SCENES ) error ( 1 , 0 , " maximum scene depth exceeded " ) ;
2025-02-19 00:55:56 +00:00
scene_clear_and_setup ( scene , - 1 , setup_fn ) ;
}
void push_scene ( int scene ) {
push_scene ( scene , scene_setup ) ;
2025-02-18 21:03:37 +00:00
}
void replace_scene ( int scene ) {
2025-02-19 00:55:56 +00:00
scene_clear_and_setup ( scene , top_scene . id , scene_setup ) ;
2025-02-18 21:03:37 +00:00
}
void pop_scene ( ) {
if ( scene_depth = = 1 ) error ( 1 , 0 , " no scenes left " ) ;
scene_depth - - ;
// any cleanup?
2025-02-19 00:55:56 +00:00
need_rerender = true ;
2025-02-17 19:11:44 +00:00
}
2025-02-19 02:09:45 +00:00
struct object * scene_add_object ( struct scene * sc , int id , int x , int y , int width , int height , const char * pixels ) {
2025-02-17 19:11:44 +00:00
if ( id = = 0 ) error ( 1 , 0 , " scene_add_object: id 0 is invalid " ) ;
2025-02-19 02:09:45 +00:00
struct object * objects = sc - > objects ;
2025-02-18 21:03:37 +00:00
for ( int i = 0 ; i < MAX_OBJECTS_PER_SCENE ; i + + ) {
2025-02-17 19:11:44 +00:00
if ( objects [ i ] . id = = 0 ) {
objects [ i ] . id = id ;
objects [ i ] . x = x ;
objects [ i ] . y = y ;
objects [ i ] . width = width ;
objects [ i ] . height = height ;
2025-02-19 03:49:32 +00:00
objects [ i ] . pixels = pixels ? ( const char * ) get_decompressed_sprite ( ( const unsigned char * ) pixels ) - > pixels : nullptr ;
2025-02-18 22:11:20 +00:00
return & objects [ i ] ;
2025-02-17 19:11:44 +00:00
}
}
error ( 1 , 0 , " too many game objects " ) ;
}
2025-02-17 16:03:14 +00:00
2025-02-19 02:09:45 +00:00
void scene_load_predef ( struct scene * sc , const struct level_predef_data * predef ) {
2025-02-18 22:11:20 +00:00
for ( const struct level_clickregion * cr = predef - > clickregions ; cr - > num_edges ; cr + + ) {
2025-02-19 02:09:45 +00:00
struct object * obj = scene_add_object ( sc , cr - > id , cr - > x , cr - > y , cr - > width , cr - > height , nullptr ) ;
2025-02-18 22:11:20 +00:00
obj - > clickregion = cr ;
}
}
2025-02-18 01:04:17 +00:00
struct object * find_object_by_id ( int id ) {
2025-02-18 21:03:37 +00:00
struct object * objects = top_scene . objects ;
for ( int i = 0 ; i < MAX_OBJECTS_PER_SCENE ; i + + ) {
2025-02-18 01:04:17 +00:00
if ( objects [ i ] . id = = id )
return & objects [ i ] ;
}
return nullptr ;
}
struct script * scene_add_script ( int id , bool interrupt_existing ) {
2025-02-18 21:03:37 +00:00
struct script * scripts = top_scene . scripts ;
for ( int i = 0 ; i < MAX_SCRIPTS_PER_SCENE ; i + + ) {
2025-02-18 01:04:17 +00:00
if ( scripts [ i ] . id = = id & & interrupt_existing ) {
// interrupt existing script?
// player walk script is safe to trivially cancel, at least
scripts [ i ] . id = 0 ;
2025-02-18 15:26:16 +00:00
if ( deliver_script_wakeup ( SCRIPT_WAKEUP_OTHER_SCRIPT , id , SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED , 0 , 0 , 0 , 0 ) )
return NULL ; // scene changed. this probably crashes the caller. should be tested or we need a better way to cancel all running operations on scene change.
2025-02-18 01:04:17 +00:00
}
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 + + ) {
2025-02-18 16:22:39 +00:00
for ( int i = 0 ; i < tri - > num_verts ; i + + ) {
2025-02-18 01:04:17 +00:00
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 ;
}
}
}
2025-02-18 16:22:39 +00:00
for ( int i = 0 ; i < tri - > num_verts ; i + + ) {
int next = ( i + 1 ) % tri - > num_verts ;
int mask = ( 1 < < i ) | ( 1 < < next ) ;
if ( ( edgeHitMask & mask ) = = mask ) {
* x = tri - > points [ next ] . x ;
* y = tri - > points [ next ] . y ;
return ;
}
}
/*
2025-02-18 01:04:17 +00:00
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 ;
2025-02-18 16:22:39 +00:00
} */
2025-02-18 01:04:17 +00:00
// 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 ) {
2025-02-18 16:22:39 +00:00
/*double edgedist[3]; // positive if outside
2025-02-18 01:04:17 +00:00
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
2025-02-18 16:22:39 +00:00
if ( edgedist [ 0 ] > = 0 & & edgedist [ 1 ] > = 0 ) return point_to_point_dist ( x , y , & tri - > points [ 0 ] ) ; // are these correct? shouldn't it be points 1,2,0?
2025-02-18 01:04:17 +00:00
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 ] ;
2025-02-18 16:22:39 +00:00
if ( edgedist [ 2 ] > = 0 ) return edgedist [ 2 ] ; */
double edgedist [ tri - > num_verts ] ;
bool all_inside = true ;
for ( int edge = 0 ; edge < tri - > num_verts ; edge + + ) {
edgedist [ edge ] = - ( x * tri - > edges [ edge ] . a + y * tri - > edges [ edge ] . b + tri - > edges [ edge ] . c ) ;
if ( edgedist [ edge ] > 0 )
all_inside = false ;
}
if ( all_inside ) return 0 ;
for ( int edge = 0 ; edge < tri - > num_verts ; edge + + ) {
int next = ( edge + 1 ) % tri - > num_verts ;
if ( edgedist [ edge ] > = 0 & & edgedist [ next ] > = 0 ) return point_to_point_dist ( x , y , & tri - > points [ next ] ) ;
}
for ( int edge = 0 ; edge < tri - > num_verts ; edge + + )
if ( edgedist [ edge ] > = 0 )
return edgedist [ edge ] ;
2025-02-18 01:04:17 +00:00
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 ;
2025-02-18 21:03:37 +00:00
struct navmesh * cur_navmesh = top_scene . navmesh ;
2025-02-18 01:04:17 +00:00
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 ) ;
2025-02-18 15:26:16 +00:00
static uint32_t player_walk_generation = 0 ;
void start_player_walk_to_point ( int targetX , int targetY ) {
player_walk_generation + + ;
struct object * player = find_object_by_id ( OBJID_PLAYER ) ;
if ( ! player ) error ( 1 , 0 , " start_player_walk_to_point: no player object " ) ;
int destTri = find_navmesh_tri ( targetX , targetY , 999999 ) ;
if ( destTri = = - 1 ) error ( 1 , 0 , " start_player_walk_to_point: no navmesh " ) ;
struct script_player_walk * player_walk_script = ( struct script_player_walk * ) scene_add_script ( OBJID_PLAYER_WALK_SCRIPT , true ) ;
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 ) ;
}
2025-02-19 00:55:56 +00:00
void standard_handle_tap ( struct scene * scene , int x , int y ) {
2025-02-18 15:26:16 +00:00
uint32_t walkgen = player_walk_generation ;
2025-02-19 00:55:56 +00:00
int sceneid = scene - > id ;
2025-02-18 21:03:37 +00:00
struct navmesh * cur_navmesh = top_scene . navmesh ;
struct object * objects = top_scene . objects ;
for ( int i = 0 ; i < MAX_OBJECTS_PER_SCENE ; i + + ) {
2025-02-18 22:11:20 +00:00
if ( ! objects [ i ] . id ) continue ;
if ( objects [ i ] . clickregion ) {
const struct level_clickregion * cr = objects [ i ] . clickregion ;
for ( int i = 0 ; i < cr - > num_edges ; i + + ) {
if ( cr - > edges [ i ] . a * x + cr - > edges [ i ] . b * y + cr - > edges [ i ] . c < 0 )
goto click_not_on_object ;
}
} else {
if ( ( unsigned int ) ( x - objects [ i ] . x ) > = objects [ i ] . width | | ( unsigned int ) ( y - objects [ i ] . y ) > = objects [ i ] . height )
goto click_not_on_object ;
2025-02-17 19:11:44 +00:00
}
2025-02-18 22:11:20 +00:00
// clicked on the object
2025-02-19 00:55:56 +00:00
onclick ( sceneid , & objects [ i ] ) ;
2025-02-18 22:11:20 +00:00
if ( top_scene . id ! = sceneid ) return ; // early exit if scene changed, and don't walk the player either
click_not_on_object : ;
2025-02-17 19:11:44 +00:00
}
2025-02-18 01:04:17 +00:00
2025-02-18 15:26:16 +00:00
if ( walkgen = = player_walk_generation ) { // no other handler moved the player
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 ] ) ;
start_player_walk_to_point ( targetX , targetY ) ;
}
2025-02-18 01:04:17 +00:00
}
}
}
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 ) {
2025-02-18 15:26:16 +00:00
int script_id = scr - > id ;
2025-02-18 01:04:17 +00:00
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 ;
2025-02-18 15:26:16 +00:00
deliver_script_wakeup ( SCRIPT_WAKEUP_OTHER_SCRIPT , script_id , SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED , 0 , 0 , 0 , 0 ) ;
2025-02-18 01:04:17 +00:00
return ;
}
2025-02-18 21:03:37 +00:00
struct navmesh * cur_navmesh = top_scene . navmesh ;
2025-02-18 01:04:17 +00:00
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 ;
2025-02-18 15:26:16 +00:00
deliver_script_wakeup ( SCRIPT_WAKEUP_OTHER_SCRIPT , script_id , SCRIPT_WAKEUP_OTHER_SCRIPT , 0 , 0 , 0 , 0 ) ;
2025-02-18 01:04:17 +00:00
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 ;
2025-02-18 16:22:39 +00:00
for ( int edge = 0 ; edge < cur_navmesh - > tris [ nmtri ] . num_verts ; edge + + ) {
2025-02-18 01:04:17 +00:00
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 ;
2025-02-18 15:26:16 +00:00
deliver_script_wakeup ( SCRIPT_WAKEUP_OTHER_SCRIPT , script_id , SCRIPT_WAKEUP_OTHER_SCRIPT_INTERRUPTED , 0 , 0 , 0 , 0 ) ;
2025-02-18 01:04:17 +00:00
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 ;
2025-02-18 15:26:16 +00:00
deliver_script_wakeup ( SCRIPT_WAKEUP_OTHER_SCRIPT , script_id , SCRIPT_WAKEUP_OTHER_SCRIPT , 0 , 0 , 0 , 0 ) ;
2025-02-18 01:04:17 +00:00
} 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 ) {
2025-02-18 21:03:37 +00:00
int scene = top_scene . id ;
struct script * scripts = top_scene . scripts ;
for ( int i = 0 ; i < MAX_SCRIPTS_PER_SCENE ; i + + ) {
2025-02-18 01:04:17 +00:00
if ( scripts [ i ] . id & & scripts [ i ] . wakeupMode = = wakeupMode & & scripts [ i ] . wakeupArg1 = = wakeupArg1 ) {
scripts [ i ] . wakeupFn ( & scripts [ i ] , wakeupType , arg1 , arg2 , arg3 , arg4 ) ;
2025-02-18 21:03:37 +00:00
if ( scene_depth = = 0 | | top_scene . id ! = scene ) return true ; // scene changed - abort early
2025-02-18 01:04:17 +00:00
}
}
return false ;
2025-02-17 19:11:44 +00:00
}
2025-02-17 16:03:14 +00:00
2025-02-18 21:03:37 +00:00
void standard_scene_render ( scene * s ) {
2025-02-19 02:17:36 +00:00
if ( s - > dim_background ) {
uint32_t * ptr = curfb ;
for ( int i = 0 ; i < 1280 * 800 ; i + + , ptr + + ) {
* ptr = ( * ptr > > 2 ) & 0x3f3f3f ;
}
}
2025-02-18 21:03:37 +00:00
struct object * objects = s - > objects ;
for ( int i = 0 ; i < MAX_OBJECTS_PER_SCENE ; i + + ) {
if ( objects [ i ] . id ! = 0 & & objects [ i ] . pixels ) {
2025-02-19 00:55:56 +00:00
blit ( objects [ i ] . x , objects [ i ] . y , objects [ i ] . width , objects [ i ] . height , ( uint32_t * ) objects [ i ] . pixels ) ;
2025-02-18 21:03:37 +00:00
}
}
}
2025-02-18 01:04:17 +00:00
2025-02-17 16:03:14 +00:00
int main ( int argc , char * * argv ) {
if ( getuid ( ) ! = 0 ) {
execlp ( " sudo " , " sudo " , argv [ 0 ] , NULL ) ;
error ( 1 , errno , " execlp " ) ;
return 1 ;
}
2025-02-17 19:11:44 +00:00
int fd = open ( " /dev/dri/card0 " , O_RDWR | O_NONBLOCK ) ;
2025-02-17 16:03:14 +00:00
if ( fd < 0 ) error ( 1 , errno , " open display " ) ;
touchscreen_fd = open ( " /dev/input/event1 " , O_RDONLY | O_NONBLOCK ) ;
if ( touchscreen_fd < 0 ) error ( 1 , errno , " open touchscreen " ) ;
if ( ioctl ( fd , DRM_IOCTL_SET_MASTER , NULL ) < 0 ) error ( 1 , errno , " set master " ) ;
// attach CRTC 51, connector 56, to a new framebuffer with a new dumbbuffer
2025-02-17 19:11:44 +00:00
// create two buffers
uint32_t * fbs [ 2 ] ;
uint32_t fb_ids [ 2 ] ;
for ( int i = 0 ; i < 2 ; i + + ) {
struct drm_mode_create_dumb createdumb = {
. height = 1280 ,
. width = 800 ,
. bpp = 32 ,
. flags = 0 ,
} ;
if ( ioctl ( fd , DRM_IOCTL_MODE_CREATE_DUMB , & createdumb ) < 0 ) error ( 1 , errno , " create dumb buffer " ) ;
if ( createdumb . pitch ! = 800 * 4 | | createdumb . size ! = 1280 * 800 * 4 )
error ( 1 , 0 , " unexpected size for dumb buffer (pitch=%d size=%d) " , ( int ) createdumb . pitch , ( int ) createdumb . size ) ;
struct drm_mode_fb_cmd2 addfb = {
. width = 800 ,
. height = 1280 ,
. pixel_format = DRM_FORMAT_XRGB8888 ,
. flags = 0 ,
. handles = { createdumb . handle , 0 , 0 , 0 } ,
. pitches = { createdumb . pitch , 0 , 0 , 0 } ,
. offsets = { 0 , 0 , 0 , 0 } ,
} ;
if ( ioctl ( fd , DRM_IOCTL_MODE_ADDFB2 , & addfb ) < 0 ) error ( 1 , errno , " add framebuffer " ) ;
struct drm_mode_map_dumb mapdumb = {
. handle = createdumb . handle
} ;
if ( ioctl ( fd , DRM_IOCTL_MODE_MAP_DUMB , & mapdumb ) < 0 ) error ( 1 , errno , " map dumb buffer " ) ;
fbs [ i ] = ( uint32_t * ) mmap ( NULL , createdumb . size , PROT_READ | PROT_WRITE , MAP_SHARED , fd , mapdumb . offset ) ;
if ( ! fbs [ i ] ) error ( 1 , errno , " mmap dumb buffer " ) ;
fb_ids [ i ] = addfb . fb_id ;
memset ( fbs [ i ] , 0 , createdumb . size ) ;
}
2025-02-17 16:03:14 +00:00
uint32_t connectors [ 1 ] = { 56 } ;
struct drm_mode_crtc crtc = { . crtc_id = 51 } ;
if ( ioctl ( fd , DRM_IOCTL_MODE_GETCRTC , & crtc ) < 0 ) error ( 1 , errno , " get crtc " ) ;
//struct drm_mode_crtc oldcrtc = crtc;
crtc . set_connectors_ptr = ( uint64_t ) connectors ;
crtc . count_connectors = 1 ;
2025-02-17 19:11:44 +00:00
crtc . fb_id = fb_ids [ 0 ] ;
2025-02-17 16:03:14 +00:00
if ( ioctl ( fd , DRM_IOCTL_MODE_SETCRTC , & crtc ) < 0 ) error ( 1 , errno , " set crtc " ) ;
int timerfd = timerfd_create ( CLOCK_MONOTONIC , TFD_NONBLOCK | TFD_CLOEXEC ) ;
if ( timerfd < 0 ) error ( 1 , errno , " timerfd_create " ) ;
{
2025-02-19 00:55:56 +00:00
# define ANIMTIMER_INTERVAL 75000000
struct itimerspec tspec = { . it_interval = { . tv_nsec = ANIMTIMER_INTERVAL } , . it_value = { . tv_nsec = ANIMTIMER_INTERVAL } } ;
2025-02-17 16:03:14 +00:00
if ( timerfd_settime ( timerfd , 0 , & tspec , NULL ) < 0 ) error ( 1 , errno , " timerfd_settime " ) ;
}
2025-02-18 21:03:37 +00:00
push_scene ( SCENE_LOBBY ) ;
2025-02-17 16:03:14 +00:00
2025-02-17 19:11:44 +00:00
int cur_buffer = 0 ;
need_rerender = true ;
bool awaiting_vblank = false ;
2025-02-17 16:03:14 +00:00
for ( ; ; ) {
struct pollfd polls [ ] = {
{ . fd = touchscreen_fd , . events = POLLIN } ,
{ . fd = timerfd , . events = POLLIN } ,
2025-02-17 19:11:44 +00:00
{ . fd = fd , . events = POLLIN } ,
2025-02-17 16:03:14 +00:00
} ;
2025-02-17 19:11:44 +00:00
const int NPOLLS = sizeof ( polls ) / sizeof ( polls [ 0 ] ) ;
static const char * pollfd_labels [ NPOLLS ] = { " touchscreen " , " animation timer " , " display " } ;
int npoll = poll ( polls , NPOLLS , 0 ) ;
2025-02-17 16:03:14 +00:00
if ( npoll < 0 ) error ( 1 , errno , " poll " ) ;
// POLLERR, POLLHUP, POLLNVAL can always be returned
2025-02-17 19:11:44 +00:00
for ( int i = 0 ; i < NPOLLS ; i + + ) {
if ( polls [ i ] . revents & ( POLLNVAL | POLLERR | POLLHUP ) ) {
if ( polls [ i ] . revents & POLLNVAL ) error ( 1 , 0 , " poll: got POLLNVAL (%s) " , pollfd_labels [ i ] ) ;
if ( polls [ i ] . revents & POLLERR ) error ( 1 , 0 , " poll: got POLLERR (%s) " , pollfd_labels [ i ] ) ;
if ( polls [ i ] . revents & POLLHUP ) error ( 1 , 0 , " poll: got POLLHUP (%s) " , pollfd_labels [ i ] ) ;
}
}
2025-02-17 16:03:14 +00:00
if ( polls [ 0 ] . revents & POLLIN ) {
poll_touchscreen ( ) ;
2025-02-17 19:11:44 +00:00
need_rerender = true ; // TODO not always
2025-02-17 16:03:14 +00:00
}
if ( polls [ 1 ] . revents & POLLIN ) {
uint64_t dummy ; // don't care about expiration count
if ( read ( timerfd , & dummy , sizeof dummy ) < 0 ) error ( 1 , errno , " read (timerfd) " ) ;
2025-02-17 19:11:44 +00:00
current_animframe + + ;
2025-02-18 01:04:17 +00:00
//printf("animation frame %u\n", (unsigned int)current_animframe);
2025-02-19 00:55:56 +00:00
if ( top_scene . animtimer_fn ) {
top_scene . animtimer_fn ( & top_scene ) ;
}
2025-02-17 19:11:44 +00:00
}
if ( polls [ 2 ] . revents & POLLIN ) {
char buf [ 256 ] ;
int nread = read ( fd , buf , sizeof buf ) ;
if ( nread < 0 ) error ( 1 , errno , " read (display) " ) ;
char * pos = buf ;
while ( pos < buf + nread ) {
struct drm_event * evthdr = ( struct drm_event * ) pos ;
pos + = evthdr - > length ;
switch ( evthdr - > type ) {
case DRM_EVENT_FLIP_COMPLETE :
awaiting_vblank = false ;
break ;
default :
fprintf ( stderr , " unknown DRM event type 0x%08 " PRIx32 , ( unsigned int ) evthdr - > type ) ;
break ;
}
}
}
2025-02-18 01:04:17 +00:00
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 ) ;
}
2025-02-17 19:11:44 +00:00
if ( need_rerender & & ! awaiting_vblank ) {
cur_buffer = 1 - cur_buffer ; // for testing, just keep writing into the same buffer
curfb = fbs [ cur_buffer ] ;
2025-02-18 21:03:37 +00:00
for ( int s = 0 ; s < scene_depth ; s + + ) {
scenes [ s ] . render_fn ( & scenes [ s ] ) ;
2025-02-17 19:11:44 +00:00
}
struct drm_mode_crtc_page_flip flipcmd = {
. crtc_id = crtc . crtc_id ,
. fb_id = fb_ids [ cur_buffer ] ,
. flags = DRM_MODE_PAGE_FLIP_EVENT , // async flip doesn't work on pinetab
} ;
if ( ioctl ( fd , DRM_IOCTL_MODE_PAGE_FLIP , & flipcmd ) < 0 )
error ( 1 , errno , " DRM page flip " ) ;
awaiting_vblank = true ;
need_rerender = false ;
2025-02-17 16:03:14 +00:00
}
}
//if(ioctl(fd, DRM_IOCTL_MODE_SETCRTC, &oldcrtc) < 0) error(1, errno, "restore crtc"); // doesn't work: EINVAL (maybe because framebuffer id is local to our connection)
}