(This tech tip was written when the latest version of the SDK was P3_SDK_V0.8)
BallPaths allow a game to make use of module-specific shots without previous knowledge of the upper playfield. BallPaths are described in the module definition file and implemented by the module driver.
The SDK documentation describes the syntax of a BallPath definition here: P3_SDK_V0.8/P3SampleApp/Documentation/html/_creating_playfield_module.html#BallPaths
The SDK documentation gives an example how to handle BallPath events here P3_SDK_V0.8/P3SampleApp/Documentation/html/_physical_features.html#DetectingShotsViaBallPaths
That snippet of code also appears in the HomeMode constructor in P3_SDK_V0.8/P3SampleApp/Assets/Scripts/Modes/SceneModes/HomeMode.cs
It is reproduced below:
// Here's an example of how to subscribe to generically-defined BallPaths in the playfield module drivers. // You might use code like this if you want your game to work with all playfield modules or if you want // to use the module drivers detection logic for when playfield shots, targets, holes, etc are hit. foreach (BallPathDefinition shot in p3.BallPaths.Values) { if (shot.ExitType == BallPathExitType.Target) { string[] strippedSwitchName = shot.StartedEvent.Split('_'); // format of switch event must be "sw_<switch name>_active" for started events string swName = strippedSwitchName[1]; add_switch_handler(swName, "active", 0, TargetHitEventHandler); } else if (shot.ExitType != BallPathExitType.Hole) { Multimorphic.P3App.Logging.Logger.Log(Multimorphic.P3App.Logging.LogCategories.Game, "Installing ShotHitHandler for : " + shot.CompletedEvent + " exitType: " + shot.ExitType.ToString()); AddModeEventHandler(shot.CompletedEvent, ShotHitEventHandler, priority); } else { Multimorphic.P3App.Logging.Logger.Log(Multimorphic.P3App.Logging.LogCategories.Game, "Installing HoleHitHandler for : " + shot.CompletedEvent); AddModeEventHandler(shot.CompletedEvent, HoleHitEventHandler, priority); } } |
In HomeMode.cs, you can see the signature of the event handlers:
private bool TargetHitEventHandler(Switch sw) { // Add target hit logic here return SWITCH_CONTINUE; } private bool ShotHitEventHandler(string evtName, object evtData) { // Add shot hit logic return SWITCH_CONTINUE; } private bool HoleHitEventHandler(string evtName, object evtData) { // Add hole hit logic here return SWITCH_CONTINUE; } |
This code can handle these two BallPaths in Heist.json
{ "Name": "RightJailTarget", "Tags": ["Target"], "ExitType": "Target", "EntranceName": "RightJailTarget", "ExitName": "RightJailTarget", "StartedEvent": "sw_RightJailTarget_active", "CompletedEvent": "sw_RightJailTarget_inactive", "EntrancePosition": [11.5,5,0], "ExitPosition": [11.5,5,0], "EntranceLEDs": [["jailRight"],], "ExitLEDs": [[""],], }, { "Name": "RightOuterLoopToHole", "Tags": ["Loop"], "ExitType": "Hole", "EntranceName": "RightOuterLoop", "ExitName": "Hole", "StartedEvent": "Evt_RightOuterLoopStarted", "CompletedEvent": "Evt_Shot_RightOuterLoopToHole", "EntrancePosition": [18,0,0], "ExitPosition": [NaN,NaN,NaN], "EntranceLEDs": [["rightOuterOrbitDown","lab","storage0","storage1","storage2","storage3","storage4","storage5","storageAndKey","bigLock"],], "ExitLEDs": [[""],], } |
This code assumes the StartedEvent of a Target BallPath must be a switch event with the syntax “sw_<switch name>_active” and the CompletedEvent of other BallPaths must be a regular mode event.
The sample code does not show this, but from the Heist definitions above, we can infer the CompletedEvent of a Target BallPath must be a switch event with the syntax “sw_<switch name>_inactive” and the StartedEvent of other BallsPaths must be a regular mode event.
In the event handlers:
Somewhat confusingly, the Heist module driver also sends a regular mode event for a Target BallPath. More specifically, it will send the mode event “sw_<switch name>_active” when the target becomes active, and it will send the mode event “sw_<switch name>_inactive” when the target becomes inactive. Heist is the only module driver that does this. Earlier module drivers rely exclusively on the switch handlers. If you want to support the earlier modules, might as well stick to switch events for Target BallPaths.
When WAMONH came out, the Target BallPaths used mode events instead of switch events, therefore breaking the convention.
{ "Name": "FoodTargetR", "Tags": ["Static", "Target"], "ExitType": "Target", "EntranceName": "FoodTargetR", "ExitName": "FoodTargetR", "StartedEvent": "Evt_ShotStarted_FoodTargetR", "CompletedEvent": "Evt_ShotMade_FoodTargetR", "EntrancePosition": [4,15,4], "ExitPosition": [NaN,NaN,NaN], "EntranceLEDs": [["FoodR"],], "ExitLEDs": [[""],], } |
The documentation and P3SampleApp were not updated in P3_SDK_V0.8.
The sample code in the documentation and HomeMode cannot handle this BallPath definition.
Gerry proposed this replacement code in the P3 Community Discord on May 5 2023:
foreach (BallPathDefinition shot in p3.BallPaths.Values) { if (shot.ExitType == BallPathExitType.Target) { // If the completed event looks like a standard switch handler definition, // parse and add a normal switch handler. Otherwise, use the completed event // with a mode2mode handler. if (shot.CompletedEvent.Contains("_inactive")) { string[] strippedSwitchName = shot.CompletedEvent.Split('_'); // format of switch event must be "sw_<switch name>_active" for started events string swName = strippedSwitchName[1]; add_switch_handler(swName, "active", 0, TargetHitEventHandler); } else { AddModeEventHandler(shot.CompletedEvent, TargetPathHitEventHandler, priority); } } else if (shot.ExitType != BallPathExitType.Hole) { Multimorphic.P3App.Logging.Logger.Log(Multimorphic.P3App.Logging.LogCategories.Game, "Installing ShotHitHandler for : " + shot.CompletedEvent + " exitType: " + shot.ExitType.ToString()); AddModeEventHandler(shot.CompletedEvent, ShotHitEventHandler, priority); } else { Multimorphic.P3App.Logging.Logger.Log(Multimorphic.P3App.Logging.LogCategories.Game, "Installing HoleHitHandler for : " + shot.CompletedEvent); AddModeEventHandler(shot.CompletedEvent, HoleHitEventHandler, priority); } } |
The TargetPathHitEventHandler method can be implemented like this:
private bool TargetPathHitEventHandler(string evtName, object evtData) { // Add target path hit logic here or move the subscription and this code into a relevant child mode to process target path hits // for your game. return SWITCH_CONTINUE; } |
This sample code can handle BallPaths for all modules currently shipping as of May 16 2023.
Notice the new code handles the CompletedEvent of Target BallPaths when it is a mode event because it cannot make any assumption on how the shot is implemented. When the Target BallPath is using the switch event syntax, it can install a handler for the StartedEvent as the previous code did because it knows the shot is a single switch.
This fix was applied to HomeMode in P3EmptyGame V0.8.1