diff --git a/Radiant/neverball.game/data/entities.ent b/Radiant/neverball.game/data/entities.ent index b1d43c352..a98bc8de3 100644 --- a/Radiant/neverball.game/data/entities.ent +++ b/Radiant/neverball.game/data/entities.ent @@ -186,6 +186,8 @@ Used for most static level geometry. Name of the author. Marks this level as a bonus level. Neverputt: time to wait after the ball has stopped before starting the next shot. Useful to ensure that moving objects won't hit the ball while the player is making the shot. +Neverputt: time to wait for the ball to stop before the player can forfeit their turn for a penalty. -1.0 means never. 0.0 means always. The default is 30.0. +Neverputt: time to wait for the ball to stop before the player automatically forfeits their turn for a penalty. -1.0 means never. The default is -1.0. Neverputt: the number of strokes required to complete the hole. diff --git a/TrenchBroom/entities.ent b/TrenchBroom/entities.ent index 7ef1e2d01..ccd0f9790 100644 --- a/TrenchBroom/entities.ent +++ b/TrenchBroom/entities.ent @@ -187,6 +187,8 @@ Used for most static level geometry. Name of the author. Marks this level as a bonus level. Neverputt: time to wait after the ball has stopped before starting the next shot. Useful to ensure that moving objects won't hit the ball while the player is making the shot. +Neverputt: time to wait for the ball to stop before the player can forfeit their turn for a penalty. -1.0 means never. 0.0 means always. The default is 30.0. +Neverputt: time to wait for the ball to stop before the player automatically forfeits their turn for a penalty. -1.0 means never. The default is -1.0. Neverputt: the number of strokes required to complete the hole. diff --git a/data/map-ckk/17.map b/data/map-ckk/17.map index ce92f3b4d..6c5c2cd65 100644 --- a/data/map-ckk/17.map +++ b/data/map-ckk/17.map @@ -1,6 +1,7 @@ // entity 0 { "classname" "worldspawn" +"can_forfeit_after" "60" // brush 0 { ( 72 640 264 ) ( 72 640 240 ) ( 72 656 264 ) mtrl/invisible 128 128 0 0.5 0.5 0 0 0 diff --git a/data/map-ckk/18.map b/data/map-ckk/18.map index b4e609c34..4582922e5 100644 --- a/data/map-ckk/18.map +++ b/data/map-ckk/18.map @@ -1,9 +1,9 @@ - // entity 0 { "classname" "worldspawn" "grad" "back/space.png" "back" "map-back/jupitor.sol" +"can_forfeit_after" "60" // brush 0 { ( -384 6016 -160 ) ( -400 6032 -160 ) ( -384 6016 -184 ) mtrl/invisible 128 128 0 0.5 0.5 0 0 0 diff --git a/data/map-iCourse/iPutt14.map b/data/map-iCourse/iPutt14.map index b4767cd8a..dc4875fd2 100644 --- a/data/map-iCourse/iPutt14.map +++ b/data/map-iCourse/iPutt14.map @@ -1,7 +1,7 @@ - // entity 0 { "classname" "worldspawn" +"can_forfeit_after" "60" // brush 0 { ( -66 -328 -128 ) ( -128 -352 -128 ) ( -70 -326 -128 ) mtrl/turf-green-small 0 0 0 0.5 0.5 0 0 0 diff --git a/data/map-iCourse/iPutt15.map b/data/map-iCourse/iPutt15.map index 83b79c928..f43ccbe80 100644 --- a/data/map-iCourse/iPutt15.map +++ b/data/map-iCourse/iPutt15.map @@ -1,7 +1,7 @@ - // entity 0 { "classname" "worldspawn" +"can_forfeit_after" "60" // brush 0 { ( -352 288 104 ) ( -326 314 80 ) ( -322 312 80 ) mtrl/turf-green-dark 0 0 0 0.5 0.5 0 0 0 diff --git a/data/map-iCourse/iPutt16.map b/data/map-iCourse/iPutt16.map index 26fd58260..842b56d45 100644 --- a/data/map-iCourse/iPutt16.map +++ b/data/map-iCourse/iPutt16.map @@ -1,7 +1,7 @@ - // entity 0 { "classname" "worldspawn" +"can_forfeit_after" "60" // brush 0 { ( -256 -288 -152 ) ( -230 -314 -152 ) ( -232 -318 -152 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0 diff --git a/data/map-iCourse/iPutt18.map b/data/map-iCourse/iPutt18.map index f9bfcf807..6b43ffac2 100644 --- a/data/map-iCourse/iPutt18.map +++ b/data/map-iCourse/iPutt18.map @@ -1,7 +1,7 @@ - // entity 0 { "classname" "worldspawn" +"can_forfeit_after" "60" // brush 0 { ( 128 768 -328 ) ( 128 768 -336 ) ( 186 826 -336 ) mtrl/invisible 0 0 0 0.5 0.5 0 0 0 diff --git a/data/map-paxed3/ovalli.map b/data/map-paxed3/ovalli.map index 740e2705d..28a9670cc 100644 --- a/data/map-paxed3/ovalli.map +++ b/data/map-paxed3/ovalli.map @@ -1,6 +1,7 @@ // entity 0 { "classname" "worldspawn" +"can_forfeit_after" "60" // brush 0 { ( -64 64 8 ) ( -64 64 -8 ) ( -64 128 -8 ) mtrl/invisible 0 0 0 0.5 0.5 134217728 0 0 diff --git a/data/map-vidski/bigpipe.map b/data/map-vidski/bigpipe.map index 062303ace..6e1c2a8b6 100644 --- a/data/map-vidski/bigpipe.map +++ b/data/map-vidski/bigpipe.map @@ -1,7 +1,7 @@ - // entity 0 { "classname" "worldspawn" +"can_forfeit_after" "60" // brush 0 { ( 0 -2304 296 ) ( 0 -2432 296 ) ( -96 -2304 296 ) mtrl/arrow-green-light 0 0 0 0.5 0.5 134217728 0 0 diff --git a/data/map-vidski/clovertop.map b/data/map-vidski/clovertop.map index 528d97dee..4750964bd 100644 --- a/data/map-vidski/clovertop.map +++ b/data/map-vidski/clovertop.map @@ -1,6 +1,7 @@ // entity 0 { "classname" "worldspawn" +"can_forfeit_after" "60" // brush 0 { ( 64 -176 -8 ) ( 64 -304 -8 ) ( -32 -176 -8 ) mtrl/arrow-green-light 128 160 0 0.5 0.5 134217728 0 0 @@ -4043,8 +4044,9 @@ } // entity 1 { -"classname" "func_train" -"target" "t2" +"classname" "info_player_start" +"origin" "0.000000 16.000000 16.000000" +"radius" "0.0625" } // entity 2 { @@ -4072,37 +4074,31 @@ } // entity 6 { -"classname" "info_player_start" -"origin" "0.000000 16.000000 16.000000" -"radius" "0.0625" -} -// entity 7 -{ "classname" "target_position" "origin" "-392.000000 376.000000 528.000000" "targetname" "target_position1" "angle" "-179.999985" } -// entity 8 +// entity 7 { "classname" "info_player_intermission" "origin" "1424.000000 -960.000000 1504.000000" "target" "target_position1" "angle" "-179.999985" } -// entity 9 +// entity 8 { "classname" "info_player_deathmatch" "origin" "0.000000 64.000000 1312.000000" "radius" "0.1375" } -// entity 10 +// entity 9 { "classname" "target_position" "origin" "272.000000 -152.000000 288.000000" "targetname" "target_position2" } -// entity 11 +// entity 10 { "classname" "info_player_intermission" "origin" "-2072.000000 1344.000000 2272.000000" diff --git a/data/map-vidski/hourglass.map b/data/map-vidski/hourglass.map index 0bccdd473..1aa3ff68f 100644 --- a/data/map-vidski/hourglass.map +++ b/data/map-vidski/hourglass.map @@ -1,7 +1,7 @@ - // entity 0 { "classname" "worldspawn" +"can_forfeit_after" "60" // brush 0 { ( -568 0 1066 ) ( -567 -18 1066 ) ( -568 0 1082 ) mtrl/edge-green-dark 240 4 0 -0.5 0.5 0 0 0 diff --git a/data/map-vidski/voodoo.map b/data/map-vidski/voodoo.map index 2aaf2e40e..31c402c07 100644 --- a/data/map-vidski/voodoo.map +++ b/data/map-vidski/voodoo.map @@ -1,7 +1,7 @@ - // entity 0 { "classname" "worldspawn" +"can_forfeit_after" "60" // brush 0 { ( 0 -1312 16 ) ( 49 -1307 16 ) ( 0 -1312 32 ) mtrl/edge-green-dark 12 0 0 0.5 0.5 0 0 0 diff --git a/putt/game.c b/putt/game.c index 654489503..99b4ef005 100644 --- a/putt/game.c +++ b/putt/game.c @@ -56,6 +56,12 @@ static float jump_dt; /* Jump duration */ static float jump_p[3]; /* Jump destination */ static float idle_t; /* Idling timeout */ +static float forf_t; /* Can forfeit timeout */ +static float auto_t; /* Automatic timeout */ + +static float putt_t; /* Current player putt time elapsed */ +static int has_forfeited; /* Has the current player forfeited? */ +static int has_auto_forf; /* Was there an auto-forfeiture? */ /*---------------------------------------------------------------------------*/ @@ -93,7 +99,11 @@ int game_init(const char *s) jump_e = 1; jump_b = 0; - idle_t = 1.0f; + idle_t = 1.0f; + forf_t = 30.0f; + auto_t = -1.0f; + + putt_t = 0.0f; view_init(); @@ -114,6 +124,10 @@ int game_init(const char *s) if (idle_t < 1.0f) idle_t = 1.0f; } + else if (strcmp(k, "can_forfeit_after") == 0) + sscanf(v, "%f", &forf_t); + else if (strcmp(k, "auto_timeout") == 0) + sscanf(v, "%f", &auto_t); } return 1; } @@ -476,6 +490,18 @@ static int game_update_state(float dt) return GAME_STOP; } + /* Test for automatic forfeit time-outs. */ + + if (auto_t >= 0.f) + { + if (putt_t >= auto_t) + { + t = 0.f; + has_auto_forf = 1; + return GAME_TIME; + } + } + return GAME_NONE; } @@ -545,6 +571,8 @@ int game_step(const float g[3], float dt) b = d; if (m) st += t; + + putt_t += t; } /* Mix the sound of a ball bounce. */ @@ -570,6 +598,13 @@ void game_putt(void) file.vary.uv[ball].v[2] = -4.f * view_e[2][2] * view_m; view_m = 0.f; + + /* Start forfeit timers. */ + + putt_t = 0.f; + + has_forfeited = 0; + has_auto_forf = 0; } /*---------------------------------------------------------------------------*/ @@ -708,5 +743,55 @@ void game_set_pos(float p[3], float e[3][3]) v_cpy(file.vary.uv[ball].e[2], e[2]); } +int game_can_forfeit(void) +{ + if (forf_t < 0.f) + return 0; + + if (has_forfeited) + return 0; + + if (putt_t >= forf_t) + return 1; + + return 0; +} + +int game_has_forced(void) +{ + return has_auto_forf; +} + +/* + * The ball is forfeited. If not already forfeited, call ‘hole_time’. + * + * The reason this isn't a direct wrapper around ‘hole_time’ is that once a + * forfeit is reached, the player can pause again, and then press escape to go + * back to the forfeiture screen, which upon entry re-executes this function. + * + * For st_fall, the logic is that if ‘hole_all’ is only called if the game + * is not currently paused, thus preventing multiple calls to ‘hole_fall’ + * for a single fall, giving multiple penalties for a single event. + * + * This trick doesn't work for manual forfeits, since they are entered from + * a paused state, not from an unpaused state, so we need to track separate + * instances differently, so that pausing from a forfeit penalty and then + * pressing escape doesn't apply another forfeiture. + */ +void game_forfeit(void) +{ + if (!has_forfeited) + { + /* Reset timers. */ + + has_forfeited = 1; + putt_t = 0; + + /* Forfeit the ball. */ + + hole_time(); + } +} + /*---------------------------------------------------------------------------*/ diff --git a/putt/game.h b/putt/game.h index 0dfb62f69..9ef0b5fc1 100644 --- a/putt/game.h +++ b/putt/game.h @@ -31,6 +31,7 @@ #define GAME_STOP 1 #define GAME_GOAL 2 #define GAME_FALL 3 +#define GAME_TIME 4 /*---------------------------------------------------------------------------*/ @@ -52,6 +53,10 @@ void game_ball(int); void game_set_pos(float[3], float[3][3]); void game_get_pos(float[3], float[3][3]); +int game_can_forfeit(void); +int game_has_forced(void); +void game_forfeit(void); + /*---------------------------------------------------------------------------*/ #endif diff --git a/putt/hole.c b/putt/hole.c index 3716105c3..247385ca3 100644 --- a/putt/hole.c +++ b/putt/hole.c @@ -389,6 +389,12 @@ void hole_fall(void) } } +void hole_time(void) +{ + /* Just use the same logic as hole_fall. */ + hole_fall(); +} + /*---------------------------------------------------------------------------*/ void hole_song(void) diff --git a/putt/hole.h b/putt/hole.h index aa106ac34..248309175 100644 --- a/putt/hole.h +++ b/putt/hole.h @@ -32,6 +32,7 @@ int hole_move(void); void hole_goal(void); void hole_stop(void); void hole_fall(void); +void hole_time(void); void hole_song(void); diff --git a/putt/main.c b/putt/main.c index 9bc99f134..428e1e919 100644 --- a/putt/main.c +++ b/putt/main.c @@ -216,7 +216,7 @@ static int loop(void) { case SDL_WINDOWEVENT_FOCUS_LOST: if (video_get_grab()) - goto_pause(&st_over); + goto_pause(&st_over, &st_time); break; case SDL_WINDOWEVENT_MOVED: diff --git a/putt/st_all.c b/putt/st_all.c index c6538532b..b105cd553 100644 --- a/putt/st_all.c +++ b/putt/st_all.c @@ -609,17 +609,20 @@ static int paused = 0; static struct state *st_continue; static struct state *st_quit; +static struct state *st_forf; #define PAUSE_CONTINUE 1 #define PAUSE_QUIT 2 +#define PAUSE_FORFEIT 3 -int goto_pause(struct state *s) +int goto_pause(struct state *quit, struct state *forf) { if (curr_state() == &st_pause) return 1; st_continue = curr_state(); - st_quit = s; + st_quit = quit; + st_forf = forf; paused = 1; return goto_state(&st_pause); @@ -636,6 +639,9 @@ static int pause_action(int i) case PAUSE_QUIT: return goto_state(st_quit); + + case PAUSE_FORFEIT: + return goto_state(st_forf); } return 1; } @@ -654,6 +660,8 @@ static int pause_enter(struct state *st, struct state *prev, int intent) if ((jd = gui_harray(id))) { gui_state(jd, _("Quit"), GUI_SML, PAUSE_QUIT, 0); + if (game_can_forfeit()) + gui_state(jd, _("Forfeit"), GUI_SML, PAUSE_FORFEIT, 0); gui_start(jd, _("Continue"), GUI_SML, PAUSE_CONTINUE, 1); } @@ -721,7 +729,7 @@ static int shared_keybd(int c, int d) if (d) { if (c == KEY_EXIT) - return goto_pause(&st_over); + return goto_pause(&st_over, &st_time); } return 1; } @@ -815,7 +823,7 @@ static int next_keybd(int c, int d) if (c == KEY_POSE) return goto_state(&st_poser); if (c == KEY_EXIT) - return goto_pause(&st_over); + return goto_pause(&st_over, &st_time); if ('0' <= c && c <= '9') num = num * 10 + c - '0'; } @@ -844,7 +852,7 @@ static int next_buttn(int b, int d) return goto_state(&st_flyby); } if (config_tst_d(CONFIG_JOYSTICK_BUTTON_B, b)) - return goto_pause(&st_over); + return goto_pause(&st_over, &st_time); } return 1; } @@ -934,7 +942,7 @@ static int flyby_buttn(int b, int d) } if (config_tst_d(CONFIG_JOYSTICK_BUTTON_B, b) || config_tst_d(CONFIG_JOYSTICK_BUTTON_START, b)) - return goto_pause(&st_over); + return goto_pause(&st_over, &st_time); } return 1; } @@ -1020,7 +1028,7 @@ static int stroke_buttn(int b, int d) if (config_tst_d(CONFIG_JOYSTICK_BUTTON_A, b)) return goto_state(&st_roll); if (config_tst_d(CONFIG_JOYSTICK_BUTTON_START, b)) - return goto_pause(&st_over); + return goto_pause(&st_over, &st_time); } else { @@ -1032,6 +1040,84 @@ static int stroke_buttn(int b, int d) /*---------------------------------------------------------------------------*/ +static int time_enter(struct state *st, struct state *prev, int intent) +{ + int id; + + const char *msg = ( + game_has_forced() + ? _("1 Stroke Timeout Penalty") + : _("1 Stroke Penalty") + ); + + if ((id = gui_label(0, msg, GUI_MED, gui_blk, gui_red))) + gui_layout(id, 0, 0); + + paused = 0; + game_forfeit(); + + hud_init(); + + return transition_slide(id, 1, intent); +} + +static int time_leave(struct state *st, struct state *next, int id, int intent) +{ + hud_free(); + return transition_slide(id, 0, intent); +} + +static void time_paint(int id, float t) +{ + game_draw(0, t); + gui_paint(id); + hud_paint(); +} + +static void time_timer(int id, float dt) +{ + gui_timer(id, dt); + + if (time_state() > 3) + { + if (hole_next()) + goto_state(&st_next); + else + goto_state(&st_score); + } +} + +static int time_click(int b, int d) +{ + if (b == SDL_BUTTON_LEFT && d == 1) + { + if (hole_next()) + goto_state(&st_next); + else + goto_state(&st_score); + } + return 1; +} + +static int time_buttn(int b, int d) +{ + if (d) + { + if (config_tst_d(CONFIG_JOYSTICK_BUTTON_A, b)) + { + if (hole_next()) + goto_state(&st_next); + else + goto_state(&st_score); + } + if (config_tst_d(CONFIG_JOYSTICK_BUTTON_B, b)) + return goto_pause(&st_over, &st_time); + } + return 1; +} + +/*---------------------------------------------------------------------------*/ + static int roll_enter(struct state *st, struct state *prev, int intent) { video_hide_cursor(); @@ -1067,6 +1153,7 @@ static void roll_timer(int id, float dt) case GAME_STOP: goto_state(&st_stop); break; case GAME_GOAL: goto_state(&st_goal); break; case GAME_FALL: goto_state(&st_fall); break; + case GAME_TIME: goto_state(&st_time); break; } } @@ -1076,7 +1163,7 @@ static int roll_buttn(int b, int d) { if (config_tst_d(CONFIG_JOYSTICK_BUTTON_B, b) || config_tst_d(CONFIG_JOYSTICK_BUTTON_START, b)) - return goto_pause(&st_over); + return goto_pause(&st_over, &st_time); } return 1; } @@ -1150,7 +1237,7 @@ static int goal_buttn(int b, int d) goto_state(&st_score); } if (config_tst_d(CONFIG_JOYSTICK_BUTTON_B, b)) - return goto_pause(&st_over); + return goto_pause(&st_over, &st_time); } return 1; } @@ -1222,7 +1309,7 @@ static int stop_buttn(int b, int d) } if (config_tst_d(CONFIG_JOYSTICK_BUTTON_B, b) || config_tst_d(CONFIG_JOYSTICK_BUTTON_START, b)) - return goto_pause(&st_over); + return goto_pause(&st_over, &st_time); } return 1; } @@ -1299,7 +1386,7 @@ static int fall_buttn(int b, int d) goto_state(&st_score); } if (config_tst_d(CONFIG_JOYSTICK_BUTTON_B, b)) - return goto_pause(&st_over); + return goto_pause(&st_over, &st_time); } return 1; } @@ -1356,7 +1443,7 @@ static int score_buttn(int b, int d) goto_state(&st_title); } if (config_tst_d(CONFIG_JOYSTICK_BUTTON_B, b)) - return goto_pause(&st_over); + return goto_pause(&st_over, &st_time); } return 1; } @@ -1547,6 +1634,19 @@ struct state st_fall = { fall_buttn }; +struct state st_time = { + time_enter, + time_leave, + time_paint, + time_timer, + NULL, + NULL, + NULL, + time_click, + shared_keybd, + time_buttn +}; + struct state st_score = { score_enter, score_leave, diff --git a/putt/st_all.h b/putt/st_all.h index 979f402f8..c4a154802 100644 --- a/putt/st_all.h +++ b/putt/st_all.h @@ -14,10 +14,11 @@ extern struct state st_roll; extern struct state st_goal; extern struct state st_stop; extern struct state st_fall; +extern struct state st_time; extern struct state st_score; extern struct state st_over; extern struct state st_pause; -int goto_pause(struct state *); +int goto_pause(struct state *, struct state *); #endif