Student/feat/better teleport - #7
StudentAlleg wants to merge 5 commits into
Conversation
Refactored ripcord messaging by introducing `SendRipcordActivated` and `SendRipcordEnded` to improve code reuse and maintainability. Enhanced ripcord targeting logic with new methods (`GetRipcordGoalPosition`, `IsClusterOnRouteTo`, `GetDockPosition`) for precise and logical target selection. Updated `FindRipcordModel` to prioritize targets based on proximity, energy availability, and fallback rules. Improved messaging and state management in `FedSrvSiteBase` and `BaseClient` to handle ripcord completions and aborts more effectively. Enhanced command view visuals to handle unknown ripcord targets and ensure accurate route drawing. Added a `bLanded` field to `RIPCORD_ABORTED` messages to distinguish between aborted and completed ripcords. Fixed edge cases in ripcord targeting and visualization. Consolidated repetitive code, optimized performance, and improved documentation for better maintainability.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved protocol compatibility and ripcord synchronization issues remain.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This pull request improves goal-aware ripcord targeting, command-plan synchronization, and route visualization.
Changes:
- Adds ripcord goal data to client/server messages.
- Synchronizes command plans and ripcord state.
- Updates group-command echoing and route rendering.
File summaries
| File | Reviewed change and final notes |
|---|---|
src/WinTrek/WinTrek.cpp |
Sends selected ripcord goal models. |
src/WinTrek/trekigc.cpp |
Processes self-issued group orders. Moderate (1 vote): group self-echo can apply orders to a non-recipient local ship. |
src/WinTrek/console.cpp |
Selects group-message echo recipients. |
src/WinTrek/cmdview.cpp |
Draws routes from active plans. Moderate (1 vote): may draw a route when the actual ripcord destination is unknown. |
src/Inc/Messages.h |
Extends wire message layouts. Moderate (2 votes): regenerate stale Wopr wrappers. Critical (2 votes): increment MSGVER from 218. |
src/Igc/shipIGC.h |
Declares ripcord goal APIs. |
src/Igc/shipIGC.cpp |
Implements ripcord target selection. Moderate (1 vote each): cross-sector targets can override local candidates; route searches ignore cowardly-route preferences. |
src/Igc/igc.h |
Updates the ship interface. |
src/FedSrv/fsship.cpp |
Sends command-plan state. Moderate (1 vote): dynamic buoys in other sectors may not be exported, losing the plan. |
src/FedSrv/FedSrv.H |
Adds the goal-aware request overload. |
src/FedSrv/FedSrv.CPP |
Handles ripcord requests and replication. Moderate (1 vote): unseen objects may be marked visible without export. Moderate (2 votes): abort notifications may be duplicated. Moderate (1 vote): activation notifications may be duplicated. |
src/clintlib/clintlib.h |
Serializes client ripcord goals. |
src/clintlib/appmsg.cpp |
Applies command and ripcord updates. Moderate (1 vote): joining clients may lack the active ripcord target. |
Review details
Suppressed comments (8)
src/FedSrv/FedSrv.CPP:13903
- Marking an unseen asteroid/station/warp visible here suppresses the normal export path, but this block never calls
ExportObjfor those types. A client that only knows the object through thisORDER_CHANGEtherefore gets a valid ID butGetModelreturns null; becauseSeenBySideis now true, later spotting/export logic will skip it as well. Export the target to the same recipient before marking it visible, or defer the visibility change until it can be exported.
IsideIGC* psideShip = pship->GetSide();
if (ptarget && psideShip && !ptarget->SeenBySide(psideShip))
{
ObjectType otTarget = ptarget->GetObjectType();
if ((otTarget == OT_asteroid) || (otTarget == OT_station) || (otTarget == OT_warp))
ptarget->SetSideVisibility(psideShip, true);
src/FedSrv/FedSrv.CPP:13545
- The side-group send above already reaches the player's own side, and a player currently in
pclusterShipis also inGetGroupSectorFlying(pclusterShip). This second send therefore delivers a duplicateRIPCORD_ACTIVATE, causing duplicate ripcord text/effect updates for everyone in that sector; skip this send when both groups are the same recipient.
if (pfsShip->IsPlayer() && (pmodelOld == NULL))
SendRipcordActivated(pship, pmodelNew, pcluster,
GetGroupSectorFlying(pclusterShip));
src/FedSrv/fsship.cpp:874
- A plan can be a dynamic waypoint buoy in a different sector. The buoy export performed when a player enters a sector only sends buoys whose cluster equals the current
pcluster, so a client that missed the original ORDER_CHANGE receives this SSU butGetModel(pfmSSU->otPlan, pfmSSU->oidPlan)returns NULL and the new plan is lost. Export referenced dynamic targets before this update (or send enough plan data to reconstruct them).
//And the plan, which is the slot the ship is actually flying.
ImodelIGC* pmodelPlan = pshipExist->GetCommandTarget(c_cmdPlan);
pfmSSU->otPlan = pmodelPlan ? pmodelPlan->GetObjectType() : NA;
pfmSSU->oidPlan = pmodelPlan ? pmodelPlan->GetObjectID() : NA;
pfmSSU->cidPlan = pshipExist->GetCommandID(c_cmdPlan);
src/Igc/shipIGC.cpp:3923
PickRipcordGoalreturns the first visible selection/order even when neither model is inpcluster, because that model is used for the route test. However,FindRipcordModelimmediately treats any returned ship or probe aspmodelRipcordwithout checking its cluster, so a selected carrier/probe in another sector can make a rip requested intopclustertarget that object outside the requested sector and skip all local candidates. Keep a separate in-destination goal for direct target selection, or gate the direct-target branch withpigc->GetCluster(this, pmodelGoal) == pcluster.
ImodelIGC* pmodelGoal = PickRipcordGoal(pcluster);
src/Igc/shipIGC.cpp:3783
- These route searches hard-code
bCowardly=false, whileCommandGeo::BuildRoutepassespship->GetPilotType() < c_ptCarrierfor the same route (src/WinTrek/cmdview.cpp:385,466). A miner or fighter can therefore choose a ripcord target using a route through sectors that its actual route avoids, making the selected teleport disagree with the path drawn to the player. Use the ship's cowardly-route preference consistently in both searches.
PathList* ppath = FindRouteList(pclusterShip, GetPosition(), GetSide(), pmodelGoal, false);
src/WinTrek/cmdview.cpp:414
bRipcordOriginKnownonly gates CASE 2 below. If the fallbackporiginModel == pshipis used andptargetis also in the viewed cluster, CASE 1 still draws a normal ship-to-target route even though the actual rip destination is unknown; this contradicts the new comment that there is no route to draw in that case. Skip route construction for an active ship whose ripcord model is the stand-in, or handle it before the case analysis.
bool bRipcordOriginKnown = pship->fRipcordActive() &&
(poriginModel != (ImodelIGC*)pship);
src/WinTrek/trekigc.cpp:4868
BaseClient::SendChatinvokesReceiveChatlocally for the singleCHAT_GROUPecho even when the local ship is not in the selected group;console.cppdeliberately chooses the first group member aspshipEchoin that case. This condition checks only the recipient type and sender, so the local pilot applies a group order to their own ship despite not being a recipient. RequireoidRecipientto equal the local ship for theCHAT_GROUPself-echo case.
Command cmd = ((ctRecipient == CHAT_INDIVIDUAL) || (ctRecipient == CHAT_GROUP)) &&
(pshipSender == trekClient.GetShip())
src/clintlib/appmsg.cpp:1165
- A client entering or re-viewing a sector after the original
RIPCORD_ACTIVATEwas sent never receives the ripcord target: the snapshot only carriesbIsRipcording. This branch therefore installs the ship stand-in even when the teleport is known to the side, soCommandGeocannot draw the ripcord route for that client. Include the ripcord target in the snapshot or send an activation to the joining connection.
if (!pfmSSU->bIsRipcording)
ship->SetRipcordModel(NULL);
else if (ship->GetRipcordModel() == NULL)
ship->SetRipcordModel(ship); //Just has to be a valid pointer
- Files reviewed: 13/13 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| SectorID sidRipcord; | ||
|
|
||
| //Where in that sector the pilot is trying to end up, so the rip can pick the teleport | ||
| //that leaves them the least left to fly. The client has to say, because the thing it is | ||
| //flying to is often a waypoint it minted itself - a buoy the server has never been given, | ||
| //whose ID means nothing here - and then the ship's command slots look empty from the | ||
| //server's side. bHasGoal is false when the pilot asked for the sector as a whole, which | ||
| //is the server's own to answer (it aims at a base they could dock at). | ||
| bool bHasGoal; | ||
| Vector positionGoal; |
| for (int i = 0; i < 2; i++) | ||
| { | ||
| CFMRecipient* precipient = (i == 0) | ||
| ? (CFMRecipient*)(pfsSide ? pfsSide->GetGroup() : NULL) | ||
| : (CFMRecipient*)(pcluster ? GetGroupSectorFlying(pcluster) : NULL); |
| //Where in that sector the pilot is trying to end up, so the rip can pick the teleport | ||
| //that leaves them the least left to fly. The client has to say, because the thing it is | ||
| //flying to is often a waypoint it minted itself - a buoy the server has never been given, | ||
| //whose ID means nothing here - and then the ship's command slots look empty from the | ||
| //server's side. bHasGoal is false when the pilot asked for the sector as a whole, which |
… scope - MSGVER 218 -> 219. RIPCORD_REQUEST, RIPCORD_ABORTED and SINGLE_SHIP_UPDATE all changed shape on this branch; without the bump a mixed client and server read each other's payloads at the wrong offsets. - Regenerated the checked-in Wopr interop wrappers for those three messages. The SINGLE_SHIP_UPDATE one was already several fields behind, and the RIPCORD_REQUEST constructor left the new goal fields uninitialised. - RIPCORD_ACTIVATE and RIPCORD_ABORTED are sent to the ship's side and to its sector, and a pilot is in both, so each arrived twice: two "Ripcording to X" lines, two abort sounds, and a countdown restarted halfway through. The client now ignores a message that tells it what it already knows. - SINGLE_SHIP_UPDATE carries the ripcord target as well as the fact. A client arriving after the rip started had only ever been told "ripcording", so the command view could not draw its route and fell back on the stand-in. - A ripcording ship whose teleport we cannot name now draws no route at all. The stand-in only suppressed the cross-sector case, so a target in the viewed sector still drew a line from where the ship is standing. - The rip's direct-target branch now only takes a selected carrier or probe that is in the sector being searched. Ripping to a sector with something selected elsewhere went to the selection instead. - Both ripcord route searches ask for a cowardly route the same way the drawn route does, so the teleport picked sits on the line the pilot is shown. - A player entering a sector is now sent the buoys that ships there are flying to wherever those buoys are, not only the ones in the sector. A waypoint one sector over could not be looked up, and that ship's order was dropped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VuZJ85CauMceYcVwgkrdHG
There was a problem hiding this comment.
🔵 Needs a closer look
Unresolved moderate issues remain in state synchronization, ripcord routing, command echo handling, and ripcord cleanup.
Review details
Suppressed comments (7)
Previously missed (1) — in code that hasn't changed since the last review.
src/WinTrek/trekigc.cpp:4868
BaseClient::SendChatinvokesReceiveChatlocally for everyCHAT_GROUPmessage, including the fallback first-member send used when the sender is not in the group. With this condition, a self-sent group command addressed to another member is treated asc_cmdAccepted, so commanding a group that does not include the player installs the order on the sender too. Restrict the group case to the local echo whoseoidRecipientis the player's ship (or otherwise carry the membership/echo information).
src/FedSrv/fsship.cpp:566
- The snapshot is built before the drone cleanup at lines 585-600. When a drone reaches its destination cluster,
c_cmdPlancan still be the cluster buoy here even though the server immediately clears it; side clients receive the laterORDER_CHANGE, but allied/sector-only recipients do not and retain a stale plan and buoy consumer. Build the snapshot after that cleanup or suppress the plan for this transition.
ImodelIGC* pmodelPlan = m_pShip->GetCommandTarget(c_cmdPlan);
pfmSSU->otPlan = pmodelPlan ? pmodelPlan->GetObjectType() : NA;
pfmSSU->oidPlan = pmodelPlan ? pmodelPlan->GetObjectID() : NA;
pfmSSU->cidPlan = m_pShip->GetCommandID(c_cmdPlan);
src/FedSrv/fsship.cpp:566
- This path sends the update straight to
GetGroupSectorFlyingand does not perform the buoy export thatCFSPlayer::SetClusterperforms below. If a remote or allied client is already in the destination sector and missed the side's earlierORDER_CHANGE, an out-of-sectorc_cmdPlanbuoy cannot be resolved fromotPlan/oidPlan, so the client stores a null plan and loses the route. Export the referenced buoy to this recipient before flushing the update, or otherwise guarantee that it is present.
ImodelIGC* pmodelPlan = m_pShip->GetCommandTarget(c_cmdPlan);
pfmSSU->otPlan = pmodelPlan ? pmodelPlan->GetObjectType() : NA;
pfmSSU->oidPlan = pmodelPlan ? pmodelPlan->GetObjectID() : NA;
pfmSSU->cidPlan = m_pShip->GetCommandID(c_cmdPlan);
src/Igc/shipIGC.cpp:3787
- This recomputes the route from the ship's current position, but the actual route can already have a committed first hop in
GetWaypointWarp;CommandGeo::BuildRouteuses that warp before searching the remainder. After a drone has moved far enough for another aleph to become cheaper, this test can mark a sector as on-route when the ship will not fly there, making ripcord target selection diverge from the route shown to players. Incorporate the committed warp before searching the remainder, asBuildRoutedoes.
PathList* ppath = FindRouteList(pclusterShip, GetPosition(), GetSide(), pmodelGoal,
IsCowardlyRoute());
src/Igc/shipIGC.cpp:3754
PickRipcordGoalonly examinesc_cmdCurrentandc_cmdAccepted, even thoughc_cmdPlanis the slot the ship actually flies and is what determines the requested cluster inResetWaypoint. When a drone/miner has a plan target different from its standing order, ripcord selection ignores that target and falls back to the wrong point (often a dock), so it can choose a teleport away from the route it is following. Include the plan slot with an explicit priority appropriate to player selections.
//Selection first, then the standing order, so that a specifically selected target still
//wins when both are in the sector being ripped to.
ImodelIGC* pcandidates[2] = { m_commandTargets[c_cmdCurrent], m_commandTargets[c_cmdAccepted] };
src/WinTrek/console.cpp:1497
- If the group contains the local ship,
pshipEchois set to it and this sendsCHAT_GROUP.BaseClient::SendChatimmediately callsReceiveChatfor that type and also sends the message to the server; the server echoes it back asCHAT_INDIVIDUAL, so the local chat/order is processed twice. Use a no-echo send for the local recipient when connected, or otherwise suppress the local receive while retaining the server echo.
(pship == pshipEcho) ? CHAT_GROUP : CHAT_GROUP_NOECHO,
src/clintlib/appmsg.cpp:1184
- When a landing
SINGLE_SHIP_UPDATEarrives beforeRIPCORD_ABORTED—the server sends the update duringSetClusterand the ended message afterward—this clears the model but leaves the visual ripcord countdown active. The later abort handler returns early because the model is already null, so the effect can remain indefinitely. Clear the ripcord timer in this branch as well.
if (!pfmSSU->bIsRipcording)
ship->SetRipcordModel(NULL);
- Files reviewed: 15/15 changed files
- Comments generated: 0 new
- Review effort level: Lite
…fect - PickRipcordGoal now considers c_cmdPlan as well as the selection and the standing order. The plan is the slot the ship actually flies, so a drone or miner whose plan has moved on (a full miner heading home) no longer has the rip aimed at a target it has left behind. - IsClusterOnRouteTo honours a committed waypoint warp the way CommandGeo::BuildRoute does, so the rip stops aiming off the route line once the ship has moved far enough for another aleph to look cheaper. - CFSShip::SetCluster clears an arrived drone's cluster buoy before building the snapshot it broadcasts, instead of after, so sector-only recipients are not left holding a plan and a buoy consumer the server has already dropped. - The same path exports an out-of-sector accepted/plan buoy to the ship's side before the snapshot names it, so a client that joined after the ORDER_CHANGE can still resolve the order and draw the route. - A landing SINGLE_SHIP_UPDATE clears the ripcord countdown effect as well as the model; RIPCORD_ABORTED returns early once the model is gone, which left the effect running indefinitely when the update arrived first. - Engaging autopilot from an order addressed to ourselves plays the sound only when it was not already on: the server echoes the order back to us, so the handler runs twice for one keypress. Not changed: the review's claim that a CHAT_GROUP echo installs the order on the sender when the group excludes them. ReceiveChat gates that block on bForMe, which for CHAT_GROUP is oidRecipient == our own ship. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VuZJ85CauMceYcVwgkrdHG
No description provided.