From a742b6c067d2c220b1a11c0701d8e281a257c5d5 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Tue, 10 Dec 2024 15:46:50 -0500 Subject: [PATCH 1/4] add missing newline --- freespace2/freespace.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freespace2/freespace.cpp b/freespace2/freespace.cpp index 5d39aa60fdd..4e1f1020bbc 100644 --- a/freespace2/freespace.cpp +++ b/freespace2/freespace.cpp @@ -1086,7 +1086,7 @@ void game_level_init() Campaign_ending_via_supernova = 0; load_gl_init = (time(nullptr) - load_gl_init); - mprintf(("Game_level_init took %ld seconds", load_gl_init)); + mprintf(("Game_level_init took %ld seconds\n", load_gl_init)); //WMC - Init multi players for level if (Game_mode & GM_MULTIPLAYER && Player != nullptr) { From 02cbfbc34703e1d72cd711c6b3d9c0a78d0e97fc Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Tue, 10 Dec 2024 20:00:25 -0500 Subject: [PATCH 2/4] enhance the add-goal and remove-goal sexps 1. Allow the goal priority to be an evaluated number, not just a literal number. This allows the goal priority to be obtained from operators or calculations. 2. Since the priority can now be evaluated, it can be NaN. In that case, default to the max priority. 3. Consolidate `CDR(CDR(...))` to `CDDR()` in the aigoal implementation 4. Clarify that `remove-goal` removes the first goal of a given type and priority, not necessarily the first fully matching goal. 5. Enhance `remove-goal` to remove all goals matching the constraints, not just the first goal. 6. Allow `remove-goal` to disregard priority when checking for matching goals. --- code/ai/aigoals.cpp | 152 ++++++++++++++++++++++++++++---------------- code/ai/aigoals.h | 2 +- code/parse/sexp.cpp | 37 +++++++---- 3 files changed, 124 insertions(+), 67 deletions(-) diff --git a/code/ai/aigoals.cpp b/code/ai/aigoals.cpp index 4953b42fc82..1240ac492c0 100644 --- a/code/ai/aigoals.cpp +++ b/code/ai/aigoals.cpp @@ -907,6 +907,7 @@ void ai_add_wing_goal_player( int type, int mode, int submode, const char *shipn void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, const char *actor_name ) { int node, dummy, op; + bool priority_is_nan = false, priority_is_nan_forever = false; Assert ( Sexp_nodes[sexp].first != -1 ); node = Sexp_nodes[sexp].first; @@ -932,7 +933,7 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index); // waypoint path name; - aigp->priority = atoi( CTEXT(CDR(CDR(node))) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); aigp->ai_mode = AI_GOAL_WAYPOINTS; if ( op == OP_AI_WAYPOINTS_ONCE ) aigp->ai_mode = AI_GOAL_WAYPOINTS_ONCE; @@ -950,9 +951,9 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index ); // store the name of the subsystem in the docker.name field for now -- this field must get // fixed up when the goal is valid since we need to locate the subsystem on the ship's model - aigp->docker.name = ai_get_goal_target_name(CTEXT(CDR(CDR(node))), &dummy); + aigp->docker.name = ai_get_goal_target_name(CTEXT(CDDR(node)), &dummy); aigp->flags.set(AI::Goal_Flags::Subsys_needs_fixup); - aigp->priority = atoi( CTEXT(CDR(CDR(CDR(node)))) ); + aigp->priority = eval_num(CDDDR(node), priority_is_nan, priority_is_nan_forever); break; case OP_AI_DISABLE_SHIP: @@ -960,7 +961,7 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons aigp->ai_mode = (op == OP_AI_DISABLE_SHIP) ? AI_GOAL_DISABLE_SHIP : AI_GOAL_DISABLE_SHIP_TACTICAL; aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index ); aigp->ai_submode = -SUBSYSTEM_ENGINE; - aigp->priority = atoi( CTEXT(CDR(CDR(node))) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); break; case OP_AI_DISARM_SHIP: @@ -968,27 +969,27 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons aigp->ai_mode = (op == OP_AI_DISARM_SHIP) ? AI_GOAL_DISARM_SHIP : AI_GOAL_DISARM_SHIP_TACTICAL; aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index ); aigp->ai_submode = -SUBSYSTEM_TURRET; - aigp->priority = atoi( CTEXT(CDR(CDR(node))) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); break; case OP_AI_WARP_OUT: aigp->ai_mode = AI_GOAL_WARP; - aigp->priority = atoi( CTEXT(CDR(node)) ); + aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever); break; // the following goal is obsolete, but here for compatibility case OP_AI_WARP: aigp->ai_mode = AI_GOAL_WARP; aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index); // waypoint path name; - aigp->priority = atoi( CTEXT(CDR(CDR(node))) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); break; case OP_AI_UNDOCK: - aigp->priority = atoi( CTEXT(CDR(node)) ); + aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever); // Goober5000 - optional undock with something - if (CDR(CDR(node)) != -1) - aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(CDR(node))), &aigp->target_name_index ); + if (CDDR(node) != -1) + aigp->target_name = ai_get_goal_target_name( CTEXT(CDDR(node)), &aigp->target_name_index ); aigp->ai_mode = AI_GOAL_UNDOCK; aigp->ai_submode = AIS_UNDOCK_0; @@ -998,7 +999,7 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons { aigp->ai_mode = AI_GOAL_REARM_REPAIR; aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index); - aigp->priority = atoi( CTEXT(CDR(CDR(node))) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); // this goal needs some extra setup // if this doesn't work, the goal will be immediately removed @@ -1016,36 +1017,36 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons case OP_AI_STAY_STILL: aigp->ai_mode = AI_GOAL_STAY_STILL; aigp->target_name = ai_get_goal_target_name(CTEXT(CDR(node)), &aigp->target_name_index); // waypoint path name; - aigp->priority = atoi( CTEXT(CDR(CDR(node))) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); break; case OP_AI_DOCK: aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index ); - aigp->docker.name = ai_add_dock_name(CTEXT(CDR(CDR(node)))); - aigp->dockee.name = ai_add_dock_name(CTEXT(CDR(CDR(CDR(node))))); - aigp->priority = atoi( CTEXT(CDR(CDR(CDR(CDR(node))))) ); + aigp->docker.name = ai_add_dock_name(CTEXT(CDDR(node))); + aigp->dockee.name = ai_add_dock_name(CTEXT(CDDDR(node))); + aigp->priority = eval_num(CDDDDR(node), priority_is_nan, priority_is_nan_forever); aigp->ai_mode = AI_GOAL_DOCK; aigp->ai_submode = AIS_DOCK_0; // be sure to set the submode break; case OP_AI_CHASE_ANY: - aigp->priority = atoi( CTEXT(CDR(node)) ); + aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever); aigp->ai_mode = AI_GOAL_CHASE_ANY; break; case OP_AI_PLAY_DEAD: - aigp->priority = atoi( CTEXT(CDR(node)) ); + aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever); aigp->ai_mode = AI_GOAL_PLAY_DEAD; break; case OP_AI_PLAY_DEAD_PERSISTENT: - aigp->priority = atoi( CTEXT(CDR(node)) ); + aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever); aigp->ai_mode = AI_GOAL_PLAY_DEAD_PERSISTENT; break; case OP_AI_KEEP_SAFE_DISTANCE: - aigp->priority = atoi( CTEXT(CDR(node)) ); + aigp->priority = eval_num(CDR(node), priority_is_nan, priority_is_nan_forever); aigp->ai_mode = AI_GOAL_KEEP_SAFE_DISTANCE; break; @@ -1055,7 +1056,7 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons bool is_nan, is_nan_forever; aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index ); - aigp->priority = atoi( CTEXT(CDDR(node)) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); // distance from ship if ( CDDDR(node) < 0 ) @@ -1091,7 +1092,7 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons case OP_AI_IGNORE: case OP_AI_IGNORE_NEW: aigp->target_name = ai_get_goal_target_name( CTEXT(CDR(node)), &aigp->target_name_index ); - aigp->priority = atoi( CTEXT(CDR(CDR(node))) ); + aigp->priority = eval_num(CDDR(node), priority_is_nan, priority_is_nan_forever); if ( op == OP_AI_CHASE ) { aigp->ai_mode = AI_GOAL_CHASE; @@ -1141,7 +1142,7 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons localnode = CDR(localnode); } - aigp->priority = atoi( CTEXT(localnode) ); + aigp->priority = eval_num(localnode, priority_is_nan, priority_is_nan_forever); aigp->lua_ai_target = { std::move(target), luaAIMode->sexp.getSEXPArgumentList(CDR(localnode)) }; } @@ -1150,8 +1151,8 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons } } - if ( aigp->priority > MAX_GOAL_PRIORITY ) { - nprintf (("AI", "bashing sexpression priority of goal %s from %d to %d.\n", CTEXT(node), aigp->priority, MAX_GOAL_PRIORITY)); + if ( aigp->priority > MAX_GOAL_PRIORITY || priority_is_nan || priority_is_nan_forever ) { + nprintf (("AI", "bashing add-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, aigp->priority, MAX_GOAL_PRIORITY)); aigp->priority = MAX_GOAL_PRIORITY; } @@ -1227,9 +1228,10 @@ int ai_find_goal_index( ai_goal* aigp, int mode, int submode, int priority ) /* Remove a goal from the given goals structure * Returns the index of the goal that it clears out. - * This is important so that if active_goal == index you can set AI_GOAL_NONE + * This is important so that if active_goal == index you can set AI_GOAL_NONE. + * NOTE: Callers should check the value of remove_more. If it is true, the function should be called again. */ -int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp ) +int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more ) { /* Sanity check */ Assert( Sexp_nodes[ sexp ].first != -1 ); @@ -1245,115 +1247,145 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp ) /* The operator to use */ int op = get_operator_const( node ); + // since this logic is common to all goals removed by the remove-goal sexp + auto eval_priority_et_seq = [sexp, &remove_more](int n, int priority_if_no_n = -1)->int + { + bool _priority_is_nan = false, _priority_is_nan_forever = false; + + int _priority = (n >= 0) ? eval_num(n, _priority_is_nan, _priority_is_nan_forever) : priority_if_no_n; + n = CDR(sexp); // we want the first node after the goal sub-tree + + if (_priority > MAX_GOAL_PRIORITY || _priority_is_nan || _priority_is_nan_forever) + { + nprintf(("AI", "bashing remove-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, _priority, MAX_GOAL_PRIORITY)); + _priority = MAX_GOAL_PRIORITY; + } + + if (n >= 0) + { + remove_more = is_sexp_true(n); + n = CDR(n); + } + + if (n >= 0) + { + if (is_sexp_true(n)) + _priority = -1; + n = CDR(n); + } + + return _priority; + }; + /* We now need to determine what the mode and submode values are*/ switch( op ) { case OP_AI_WAYPOINTS_ONCE: goalmode = AI_GOAL_WAYPOINTS_ONCE; - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); break; case OP_AI_WAYPOINTS: goalmode = AI_GOAL_WAYPOINTS; - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); break; case OP_AI_DESTROY_SUBSYS: goalmode = AI_GOAL_DESTROY_SUBSYSTEM; - priority = ( CDR( CDR( CDR(node) ) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( CDR( node ) ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDDR(node)); break; case OP_AI_DISABLE_SHIP: case OP_AI_DISABLE_SHIP_TACTICAL: goalmode = (op == OP_AI_DISABLE_SHIP) ? AI_GOAL_DISABLE_SHIP : AI_GOAL_DISABLE_SHIP_TACTICAL; - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); break; case OP_AI_DISARM_SHIP: case OP_AI_DISARM_SHIP_TACTICAL: goalmode = (op == OP_AI_DISARM_SHIP) ? AI_GOAL_DISARM_SHIP : AI_GOAL_DISARM_SHIP_TACTICAL; - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); break; case OP_AI_WARP_OUT: goalmode = AI_GOAL_WARP; - priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1; + priority = eval_priority_et_seq(CDR(node)); break; case OP_AI_WARP: goalmode = AI_GOAL_WARP; - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); break; case OP_AI_UNDOCK: goalmode = AI_GOAL_UNDOCK; goalsubmode = AIS_UNDOCK_0; - priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1; + priority = eval_priority_et_seq(CDR(node)); break; case OP_AI_STAY_STILL: goalmode = AI_GOAL_STAY_STILL; - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); break; case OP_AI_DOCK: goalmode = AI_GOAL_DOCK; goalsubmode = AIS_DOCK_0; - priority = ( CDR( CDR( CDR( CDR(node) ) ) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( CDR( CDR( node ) ) ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDDDR(node)); break; case OP_AI_CHASE_ANY: goalmode = AI_GOAL_CHASE_ANY; - priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1; + priority = eval_priority_et_seq(CDR(node)); break; case OP_AI_PLAY_DEAD: case OP_AI_PLAY_DEAD_PERSISTENT: goalmode = (op == OP_AI_PLAY_DEAD) ? AI_GOAL_PLAY_DEAD : AI_GOAL_PLAY_DEAD_PERSISTENT; - priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1; + priority = eval_priority_et_seq(CDR(node)); break; case OP_AI_KEEP_SAFE_DISTANCE: - priority = ( CDR(node) >= 0 ) ? atoi( CTEXT( CDR( node ) ) ) : -1; + priority = eval_priority_et_seq(CDR(node)); goalmode = AI_GOAL_KEEP_SAFE_DISTANCE; break; case OP_AI_CHASE: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); if ( wing_name_lookup( CTEXT( CDR( node ) ), 1 ) != -1 ) goalmode = AI_GOAL_CHASE_WING; else goalmode = AI_GOAL_CHASE; break; case OP_AI_GUARD: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); if ( wing_name_lookup( CTEXT( CDR( node ) ), 1 ) != -1 ) goalmode = AI_GOAL_GUARD_WING; else goalmode = AI_GOAL_GUARD; break; case OP_AI_GUARD_WING: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = AI_GOAL_GUARD_WING; break; case OP_AI_CHASE_WING: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = AI_GOAL_CHASE_WING; break; case OP_AI_CHASE_SHIP_CLASS: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = AI_GOAL_CHASE_SHIP_CLASS; break; case OP_AI_EVADE_SHIP: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = AI_GOAL_EVADE_SHIP; break; case OP_AI_STAY_NEAR_SHIP: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = AI_GOAL_STAY_NEAR_SHIP; break; case OP_AI_IGNORE: case OP_AI_IGNORE_NEW: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = (op == OP_AI_IGNORE) ? AI_GOAL_IGNORE : AI_GOAL_IGNORE_NEW; break; case OP_AI_FORM_ON_WING: - priority = 99; + priority = eval_priority_et_seq(-1, 99); goalmode = AI_GOAL_FORM_ON_WING; break; case OP_AI_FLY_TO_SHIP: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = AI_GOAL_FLY_TO_SHIP; break; case OP_AI_REARM_REPAIR: - priority = ( CDR( CDR(node) ) >= 0 ) ? atoi( CTEXT( CDR( CDR( node ) ) ) ) : -1; + priority = eval_priority_et_seq(CDDR(node)); goalmode = AI_GOAL_REARM_REPAIR; break; default: @@ -1369,7 +1401,7 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp ) localnode = CDR(localnode); } - priority = localnode >= 0 ? atoi( CTEXT(localnode) ) : -1; + priority = eval_priority_et_seq(localnode); } else { UNREACHABLE("Invalid SEXP-OP %s (number %d) for an AI goal!", Sexp_nodes[node].text, op); @@ -1381,7 +1413,10 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp ) int goalindex = ai_find_goal_index( aigp, goalmode, goalsubmode, priority ); if ( goalindex == -1 ) + { + remove_more = false; return -1; /* no more to do; */ + } /* Clear out the contents of the goal. We can't use ai_remove_ship_goal since it needs ai_info and * we've only got ai_goals */ @@ -1395,6 +1430,7 @@ void ai_remove_wing_goal_sexp(int sexp, wing *wingp) { int i; int goalindex = -1; + bool remove_more = false; // remove the ai goal for any ship that is currently arrived in the game (only if fred isn't running) if ( !Fred_running ) { @@ -1402,9 +1438,13 @@ void ai_remove_wing_goal_sexp(int sexp, wing *wingp) int num = wingp->ship_index[i]; if ( num == -1 ) // ship must have been destroyed or departed continue; - goalindex = ai_remove_goal_sexp_sub( sexp, Ai_info[Ships[num].ai_index].goals ); - if ( Ai_info[Ships[num].ai_index].active_goal == goalindex ) - Ai_info[Ships[num].ai_index].active_goal = AI_GOAL_NONE; + auto aip = &Ai_info[Ships[num].ai_index]; + + do { + goalindex = ai_remove_goal_sexp_sub(sexp, aip->goals, remove_more); + if (aip->active_goal == goalindex) + aip->active_goal = AI_GOAL_NONE; + } while (remove_more); } } @@ -1412,7 +1452,9 @@ void ai_remove_wing_goal_sexp(int sexp, wing *wingp) // there are more waves to come if ((wingp->num_waves - wingp->current_wave > 0) || Fred_running) { - ai_remove_goal_sexp_sub( sexp, wingp->ai_goals ); + do { + ai_remove_goal_sexp_sub(sexp, wingp->ai_goals, remove_more); + } while (remove_more); } } diff --git a/code/ai/aigoals.h b/code/ai/aigoals.h index 9d8cd5475f3..f37bbbb2f74 100644 --- a/code/ai/aigoals.h +++ b/code/ai/aigoals.h @@ -173,7 +173,7 @@ extern void ai_add_ship_goal_sexp( int sexp, int type, ai_info *aip ); extern void ai_add_wing_goal_sexp( int sexp, int type, wing *wingp ); extern void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, const char *actor_name); -extern int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp ); +extern int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more ); extern void ai_remove_wing_goal_sexp( int sexp, wing *wingp ); // adds goals to ships/sings through player orders diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index f8ff3f7e702..7667c49d4dc 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -433,7 +433,7 @@ SCP_vector Operators = { //AI Control Sub-Category { "add-goal", OP_ADD_GOAL, 2, 2, SEXP_ACTION_OPERATOR, }, - { "remove-goal", OP_REMOVE_GOAL, 2, 2, SEXP_ACTION_OPERATOR, }, // Goober5000 + { "remove-goal", OP_REMOVE_GOAL, 2, 4, SEXP_ACTION_OPERATOR, }, // Goober5000 { "add-ship-goal", OP_ADD_SHIP_GOAL, 2, 2, SEXP_ACTION_OPERATOR, }, { "add-wing-goal", OP_ADD_WING_GOAL, 2, 2, SEXP_ACTION_OPERATOR, }, { "clear-goals", OP_CLEAR_GOALS, 1, INT_MAX, SEXP_ACTION_OPERATOR, }, @@ -13139,12 +13139,16 @@ void sexp_remove_goal(int n) if (!ship_entry->has_shipp()) return; // ship not around anymore???? then forget it! - int goalindex = ai_remove_goal_sexp_sub(goal_node, Ai_info[ship_entry->shipp()->ai_index].goals); - if (goalindex >= 0) - { - if (Ai_info[ship_entry->shipp()->ai_index].active_goal == goalindex) - Ai_info[ship_entry->shipp()->ai_index].active_goal = AI_GOAL_NONE; - } + bool remove_more = false; + auto aip = &Ai_info[ship_entry->shipp()->ai_index]; + do { + int goalindex = ai_remove_goal_sexp_sub(goal_node, aip->goals, remove_more); + if (goalindex >= 0) + { + if (aip->active_goal == goalindex) + aip->active_goal = AI_GOAL_NONE; + } + } while (remove_more); return; } @@ -32167,12 +32171,19 @@ int query_operator_argument_type(int op, int argnum) return OPF_AI_GOAL; case OP_ADD_GOAL: - case OP_REMOVE_GOAL: if ( argnum == 0 ) return OPF_SHIP_WING; else return OPF_AI_GOAL; + case OP_REMOVE_GOAL: + if ( argnum == 0 ) + return OPF_SHIP_WING; + else if ( argnum == 1 ) + return OPF_AI_GOAL; + else + return OPF_BOOL; + case OP_COND: case OP_WHEN: case OP_EVERY_TIME: @@ -38609,10 +38620,14 @@ SCP_vector Sexp_help = { // Goober5000 { OP_REMOVE_GOAL, "Remove goal (Action operator)\r\n" - "\tRemoves a goal from a ship or wing.\r\n\r\n" - "Takes 2 arguments...\r\n" + "\tRemoves a goal from a ship or wing. Note that, by default, only the type of goal and the priority are matched. This operator " + "does not distinguish between, for example, two different waypoint goals.\r\n\r\n" + "Takes 2 to 4 arguments...\r\n" "\t1:\tName of ship or wing to remove goal from (ship/wing must be in-mission).\r\n" - "\t2:\tGoal to remove." }, + "\t2:\tGoal to remove.\r\n" + "\t3:\tWhether to remove all matching goals (optional; defaults to false; if false, only the first matching goal will be removed).\r\n" + "\t4:\tWhether to ignore the priority when matching a goal (optional).\r\n" + }, { OP_SABOTAGE_SUBSYSTEM, "Sabotage subystem (Action operator)\r\n" "\tReduces the specified subsystem integrity by the specified percentage." From 09552bb1952af872fa9f2342010a91f58e6a8f61 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Tue, 10 Dec 2024 20:46:48 -0500 Subject: [PATCH 3/4] bypass the SEXP caching code if running in FRED --- code/parse/sexp.cpp | 67 +++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/code/parse/sexp.cpp b/code/parse/sexp.cpp index 7667c49d4dc..23fb4c2a209 100644 --- a/code/parse/sexp.cpp +++ b/code/parse/sexp.cpp @@ -5725,40 +5725,46 @@ wing *eval_wing(int node) /** * Returns a number parsed from the sexp node text. - * NOTE: sexp_atoi can only be used if CTEXT was used; i.e. atoi(CTEXT(n)) + * NOTE: sexp_atoi() should only replace atoi(CTEXT(n)) - it should not replace atoi(Sexp_nodes[node].text) - see commit 9923c87bc1 */ int sexp_atoi(int node) { - Assertion(!Fred_running, "This function relies on SEXP caching which is not set up to work in FRED!"); if (node < 0) return 0; - // check cache - if (Sexp_nodes[node].cache) + // SEXP caching is not set up to work in FRED, so bypass all the caching code in that case + if (!Fred_running) { - // have we cached something else? - if (Sexp_nodes[node].cache->sexp_node_data_type != OPF_NUMBER) - return 0; + // check cache + if (Sexp_nodes[node].cache) + { + // have we cached something else? + if (Sexp_nodes[node].cache->sexp_node_data_type != OPF_NUMBER) + return 0; - return Sexp_nodes[node].cache->numeric_literal; - } + return Sexp_nodes[node].cache->numeric_literal; + } - // maybe forward to a special-arg node - if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) - { - auto current_argument = Sexp_replacement_arguments.back(); - int arg_node = current_argument.second; + // maybe forward to a special-arg node + if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) + { + auto current_argument = Sexp_replacement_arguments.back(); + int arg_node = current_argument.second; - if (arg_node >= 0) - return sexp_atoi(arg_node); + if (arg_node >= 0) + return sexp_atoi(arg_node); + } } int num = atoi(CTEXT(node)); ensure_opf_positive_is_positive(node, num); - // cache the value if it can't change later - if (!is_node_value_dynamic(node)) - Sexp_nodes[node].cache = new sexp_cached_data(OPF_NUMBER, num, -1); + if (!Fred_running) + { + // cache the value if it can't change later + if (!is_node_value_dynamic(node)) + Sexp_nodes[node].cache = new sexp_cached_data(OPF_NUMBER, num, -1); + } return num; } @@ -5768,21 +5774,24 @@ int sexp_atoi(int node) */ bool sexp_can_construe_as_integer(int node) { - Assertion(!Fred_running, "This function relies on SEXP caching which is not set up to work in FRED!"); if (node < 0) return false; - if (Sexp_nodes[node].cache && Sexp_nodes[node].cache->sexp_node_data_type == OPF_NUMBER) - return true; - - // maybe forward to a special-arg node - if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) + // SEXP caching is not set up to work in FRED, so bypass all the caching code in that case + if (!Fred_running) { - auto current_argument = Sexp_replacement_arguments.back(); - int arg_node = current_argument.second; + if (Sexp_nodes[node].cache && Sexp_nodes[node].cache->sexp_node_data_type == OPF_NUMBER) + return true; - if (arg_node >= 0) - return sexp_can_construe_as_integer(arg_node); + // maybe forward to a special-arg node + if (Sexp_nodes[node].flags & SNF_SPECIAL_ARG_IN_NODE) + { + auto current_argument = Sexp_replacement_arguments.back(); + int arg_node = current_argument.second; + + if (arg_node >= 0) + return sexp_can_construe_as_integer(arg_node); + } } return can_construe_as_integer(CTEXT(node)); From 58f90c22a8da8fc74569fb86393e7d1b63a4d6cf Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Thu, 12 Dec 2024 11:35:52 -0500 Subject: [PATCH 4/4] warn and adapt when adding and removing NaN priorities --- code/ai/aigoals.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/code/ai/aigoals.cpp b/code/ai/aigoals.cpp index 1240ac492c0..e419ce9242f 100644 --- a/code/ai/aigoals.cpp +++ b/code/ai/aigoals.cpp @@ -1151,7 +1151,11 @@ void ai_add_goal_sub_sexp( int sexp, int type, ai_info *aip, ai_goal *aigp, cons } } - if ( aigp->priority > MAX_GOAL_PRIORITY || priority_is_nan || priority_is_nan_forever ) { + if ( priority_is_nan || priority_is_nan_forever ) { + Warning(LOCATION, "add-goal tried to add %s with a NaN priority; aborting...", Sexp_nodes[CAR(sexp)].text); + ai_goal_reset(aigp); + return; + } else if ( aigp->priority > MAX_GOAL_PRIORITY ) { nprintf (("AI", "bashing add-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, aigp->priority, MAX_GOAL_PRIORITY)); aigp->priority = MAX_GOAL_PRIORITY; } @@ -1255,7 +1259,12 @@ int ai_remove_goal_sexp_sub( int sexp, ai_goal* aigp, bool &remove_more ) int _priority = (n >= 0) ? eval_num(n, _priority_is_nan, _priority_is_nan_forever) : priority_if_no_n; n = CDR(sexp); // we want the first node after the goal sub-tree - if (_priority > MAX_GOAL_PRIORITY || _priority_is_nan || _priority_is_nan_forever) + if (_priority_is_nan || _priority_is_nan_forever) + { + Warning(LOCATION, "remove-goal tried to remove %s with a NaN priority; the priority will not be used for goal comparison", Sexp_nodes[CAR(sexp)].text); + _priority = -1; + } + else if (_priority > MAX_GOAL_PRIORITY) { nprintf(("AI", "bashing remove-goal sexpression priority of goal %s from %d to %d.\n", Sexp_nodes[CAR(sexp)].text, _priority, MAX_GOAL_PRIORITY)); _priority = MAX_GOAL_PRIORITY;