[SOLVED] How to end and restart a multiplayer game (sim-eth-es)

As I can see there are several monkeys playing with sim-eth-es. I currently try to implement the game cycle. In my case (my first game mode) it will be a time limited dead match mode.

The hook of the game seems the GameServer.class and the hosting part is then GameSessionHostedService.java which also do have the list of all player entities (not equal to ship entities). I now would like to have a joinGame functionality for the first 30 seconds, after that point you can not join the game and you have to wait. And then I would have a endGame functionality after for example 5 minutes. The endGame should reset all counters and reset the old ship and place it in the start area gain for the next round.

Now my question, where should this ā€œgameā€ mode class take place? I currently did add a resetSession method to the GameSessionImpl class. But where and how do I observe those time slices (joinGame, endGame)? In the GameSessionHostedSevice? This does not have an update loop, so maybe I need a own game state with an update loop, but how does this have access to the player session? Via GameSessionHostedService maybe (I guess but a bit unsure).

EDIT: Some more ideas The GameSessionHostedService do have method ā€œstartJoiningStateā€, ā€œstopJoiningStateā€, ā€œendGameStateā€. So the game system only needs to call this in his update loop at the given time. But still unsure :slight_smile:

Hints, are welcome…

1 Like

Even though the game has a separate ā€œloginā€ and ā€œplayingā€ phase, the whole game is architected more like a persistent world. It may require significant refactoring to make it do otherwise… else you will be patching and hacking many times, I guess.

The proper way I guess would be to have three ā€œphasesā€ where there are only two phases now. The first is the current login phase but then once joined you are in a ā€œlobbyā€ phase. Once all players are in the lobby like they want you can then create the game phase. Any players joining after that just end up in the lobby and must wait.

It’s probably that the third phase is a whole new system or composite set of systems that must be added/removed from the game system manager at the time of creation/termination of that particular game session.

2 Likes

So I did some refactorings and I can now reset the game server. I added just another button in the the HostState beside joinServer and stopServer. I can successfully place my ship at its original start place. As long as I’m not too far from the initial start point it works.

But if I’m far away from the start point let’s say 200 units instead of just 10 for example, then the camera does not go back. But I can see in the ModelContainer of the ModelViewState that the ship has been placed to the origin (in my case (0, 0, -30). The camera follows the ship spatial. But somehow the PlayerMovementState still ā€œseesā€ the spatial on last position (0,0,200). I started some printf debugging and I print WorldTranslation, LocalTranslation and hashCode of the spatial in the PlayerMovementState and in the ModelViewState I print WorldTranslation and hashCode.

1746684059 (-4.5776367E-5, -4.5776367E-5, 209.18927) (-4.5776367E-5, -4.5776367E-5, 209.18927)
22:01:31,470 INFO  [MessageDebugger] Received:ComponentChangeMessage[[EntityChange[EntityId[5], Position[location=Vec3[0.0, 0.0, -30.0], facing=Quatd[0.0, 0.0, 0.0, 1.0]], class ch.artificials.bg.es.Position]]]
ModelContainer.updateObject(Entity[EntityId[5], values=[ModelType[type=2], Position[location=Vec3[0.0, 0.0, -30.0], facing=Quatd[0.0, 0.0, 0.0, 1.0]]]]) 1746684059
XXXXXXXXXXXXXXXXX 1746684059 (0.0, 0.0, -30.0)
22:01:45,318 INFO  [PlayerMovementState] onEnable()
1746684059 (-4.5776367E-5, -4.5776367E-5, 209.18927) (-4.5776367E-5, -4.5776367E-5, 209.18927)
1746684059 (-4.5776367E-5, -4.5776367E-5, 209.18927) (-4.5776367E-5, -4.5776367E-5, 209.18927)
1746684059 (-4.5776367E-5, -4.5776367E-5, 209.18927) (-4.5776367E-5, -4.5776367E-5, 209.18927)

The XXXXXXXXXX marker is the print in the ModelViewState. The ones without XXXXXXXXX marker is printed in the update loop of PlayerMovmentState.

I have to confess that I absolutely do not understand what I see. You can clearly see that spatials hashCode is the same. I do not understand why I do see the change in the ModelViewState but not in the PlayerMovementState of the same spatial…

Btw. the ship is still movable if reset, if I drive that ship (instrumental fliying ;)) near the camera then if it reaches the position (0, 0, 127) the camera suddently is over the ship (and the PlayerMovementState can see the spatial’s correct location).

What magic is going on or which concept of the sim-eth-es do I not understand?

Thanks in advance.

1 Like

I think someone else reported this also… I don’t know what causes it but I guess if you move too far or too fast then the zones get confused. It’s not supposed to be that way, though.

In your case, you can alleviate this by removing your ship from the entity system (or at least what keeps it in the physics space) when you tear down one game to start the next one.

1 Like

That was me, my use case was death and ā€˜respawning’, if you try teleporting the entity outside the zone it is ā€˜centered’ on it loses track and stops sending entity updates for that entity because the ethereal zoning doesn’t know were you are.

Mithrin

1 Like

Yeah, but the weird part of your report was that it wasn’t even working for neighboring zones… so I couldn’t figure out how that could happen given that’s exactly what happens when crossing a zone boundary, basically.

1 Like

As I have a top down view on my model instead a FPS camera the most weird part is that the ModelViewState ā€œseesā€ the move as the Position changes and the ModelContainer places the ship at the right place! But PlayerMovementState which also reads the spatial world translation to make the camera follow does not get the new world position. I mean I can see it is the same spatial by printing the hashCode.
Very confusing. But sooner or later I’ll get it I guess.

1 Like

Is PlayerMovementState using a WatchedEntity or something?

If you are removing and adding back the entity to the physics space then WatchedEntity’s BodyPosition might not be getting reset or something.

I’m just grasping at straws, though, since I’m not in a position to look through code tonight.

1 Like

That is absolutely fine :slight_smile: I hope I’ll find it my self in the end, I just wonder why the model view state does place it correctly and the camera stays as the camera should follow that spatial, but for some reason the same spatial does have the correct position in the model view state but not in player movement state. Somehow this is not explainable and I guess I look wrongly at my problem.

So far I do not delete it from physic space and readd it as the model view state does see the change (because I display my player ship, in your example you do not display the player ship).

1 Like

So I had the PlayerMovementState track the actual spatial?

Yeah, if they are different then it means they are not really the same spatial. I missed how you were checking but you should use System.identityHashCode(). It could still be wrong but it’s way less likely in the general case. I’ve never looked at Spatial’s hashcode to see if it does anything to override the System.identityHashCode().

Other than that, lots of debug printlns with some at the top of simpleUpdate() so you can tell when a frame starts and thus what order things are actually being called, etc.

1 Like

Yes you do

    @Override
    public void update( float tpf ) {

        // Update the camera position from the ship spatial
        Spatial spatial = models.getModel(shipId);
        if( spatial != null ) {
            camera.setLocation(spatial.getWorldTranslation());
        }

But in contrast to my game you do not display the spatial of your player. The models you get that way

        this.models = getState(ModelViewState.class);

And in the ModelViewState you manage this models with

    private Map<EntityId, Spatial> modelIndex = new HashMap<>();
    ...
    public Spatial getModel( EntityId id ) {
        return modelIndex.get(id);
    }

And when you change the Position component the ModelContainer (not the MobContainer) sees it and updates it. So if you ask me it is the same spatial also from the point of view of the code, but somehow … no clue so far, something I miss.

I’ll try your System.IdentityHashCode() to make sure I have the same spatial. But if that is really the same spatial it is pretty strange, maybe some system which do a ā€œforth and backā€ of the location. So I also have to improve my logging stuff a bit to printf debug that strange thing.

And I’m pretty sure if you would implement such ā€œresetā€ you will see the same in your sim-eth-es example :slight_smile: If I find the reason before you do, I’ll let you know in this thread here.

1 Like

a small note on that, using smthg like log.info (if you didn’t remove a whole logging facility ofc) for the purpose looks convenient, as sometimes another thread may react on something sooner than you reach your next checkpoint in current one, so this way (assuming you’ve placed debug outputs in that class too) you won’t at least miss that things went multiple routes here. Unless you keep all the logic completely in your head, of course :slight_smile:

1 Like

I have a warp function in my game, but only tried warping inside a zone. I’ll try warping to another zone and see if I encounter the same issue.

1 Like

Ok. I think I have it, still a bit strange but I guess the secrete is, that the ModelContainer get’s the Position component and places the spatial, but the MobContainer does not get an update from the physic system so the MobContainer simply resets the spatials new position in the MobContainer update. What is a bit strange is that the Model vanish (and I always thought that it is placed correctly without seeing that, so I can not proof it yet).
But I think it is clear now and the only way is to remove it from physic and readd it, by for example remove the control component I have. Or the like, doesn’t really matter.

Anyway I will have in the end a state machine like system which does the transitions from lobby to game to end to lobby or dead to lobby or even dead and back to game. And this will be a timing thing, as on end game I want to show the results, so it has to stay a couple of seconds in that state.

And even if I want later a wrap mode (yes I also thought of that, kind of worm holes at least my son wishes that :slight_smile:) I can do it the same way, simply remove it from physic and re-add it a frame later. Problem solved I would say.

Thanks to everybody for the hints and helpful advices!

2 Likes

cOk I now do it that way. I remove the Position (end game). When I remove the Position I can see that the physic state does remove it. Fine. Then I do reset the ship 4 seconds later by just set the new wished Position. I see the physics add it again. But… it does not help the same issue. As soon I blind flight my ship toward the old far away position (where my camera thinks my ship is) suddenly the camera reacts and sticks on the ship again.

So for the moment I’m a bit lost with the whole thing, as I do not see how I could make it work ā€œsomehowā€. I do not even understand why I do not get the new position. Is there maybe a way to trick the zone manager or force it somehow to send me the updates so that I can get in sync again? But I guess the answer is ā€œnoā€.

On physic system I see it vanish and reappear, some printf debugging output

22:23:07,854 INFO  [HostScreen] resetServer()
XXXXXXXXXXX remove Entity[EntityId[5], values=[ModelType[type=2], null, MassProperties[inverseMass=6.666666666666666E-4], SphereShape[radius=1.5, centerOffset=Vec3[0.0, 0.0, 0.0]]]]
XXXXXXXXXXX add Entity[EntityId[5], values=[ModelType[type=2], Position[location=Vec3[0.0, 0.0, 30.0], facing=Quatd[0.0, 0.0, 0.0, 1.0]], MassProperties[inverseMass=6.666666666666666E-4], SphereShape[radius=1.5, centerOffset=Vec3[0.0, 0.0, 0.0]

And also on client side the ship vanishes and later appears again as wished

XXXXXXXX remove Model Entity[EntityId[5], values=[ModelType[type=2], null]]
21:58:53,753 INFO  [MessageDebugger] Received:EntityDataMessage[2, [ComponentData[EntityId[5], [ModelType[type=2], Position[location=Vec3[0.0, 0.0, 30.0], facing=Quatd[0.0, 0.0, 0.0, 1.0]]]]]]
21:58:53,754 INFO  [MessageDebugger] Received:ComponentChangeMessage[[EntityChange[EntityId[5], Position[location=Vec3[0.0, 0.0, 30.0], facing=Quatd[0.0, 0.0, 0.0, 1.0]], class ch.artificials.bg.es.Position], EntityChange[EntityId[5], BodyPosition[null], class ch.artificials.bg.es.BodyPosition]]]
XXXXXXXX add Model Entity[EntityId[5], values=[ModelType[type=2], Position[location=Vec3[0.0, 0.0, 30.0], facing=Quatd[0.0, 0.0, 0.0, 1.0]]]]

Other ideas maybe?

1 Like

The ship vanishes on the client side probably because the Position was removed? It would be better to remove the MassProperties to get it to leave the physics space maybe… then you could see if it really was removed all the way through the zones, etc. because the client (as I recall) doesn’t care about MassProperties.

It could be that you have to create a new ship entity. The player entity is separate from the ship so this should be ok. Not OK for wormholes but would be a workaround in this case of starting new games.

1 Like

I tried different ones including MassProperties. And I see that it is removed and re-added to the physic state. The model vanishes under my camera just in the PlayerMovementState the spatial is always on the old position (the MobConatiner of the model view state does keep it there invisible, a detail I do not understand yet).

Just for my understanding, how does the ZoneManager know where my client is? Can we maybe goof the ZoneManager somehow and tell him ā€œno that client is now back in your rangeā€? Just a thought.

My last trial will be re-creating, but why does the ZoneManager then think that my client is in range again, that part I do not yet understand.

In the mean time I will crawl around via printf debugging :wink:

1 Like

Mmm… the zone manager knows where your client is because you told it what the entity ID was during initialization for that client somewhere in the hosting. I don’t remember off the top of my head where… but somewhere during ā€œjoining the sessionā€ on the server side, someone told the simethereal code what the ID of the client’s representative object was.

1 Like

Ok then I think I really need to remove the entity and use a new one, as the ZoneManager still ā€œthinksā€ that this entity is on the same position on client side. Would be cool for the future to have a way to ā€œconvinceā€ the ZoneManger that an entity now is on a different location/zone what so ever.

So when I create a new ship, I have to tell the simethereal code somehow the new client’s representative ID… Ok I’ll dig a bit in your example to pin point that :slight_smile:

Honestly even wrap mode I can do that way as there it is a de-materialize / re-materialize of the ship :wink:

1 Like

Maybe you are interested in how I solved my problem

public class GameSessionHostedService extends AbstractHostedConnectionService {
    ...
    public void endState() {
        players.forEach((player) -> {
            player.endSession();
        });
    }

    public void resetState() {
        players.forEach((player) -> {
            player.resetSession();
        });
    }

I call endState() and 5 seconds later I call resetState()

    private class GameSessionImpl implements GameSession {
        ...
        public void resetSession() {
            TeamType team = ed.getComponent(playerEntity, TeamType.class);
            if (this.shipEntity == null) {
                switch (team.getTypeName(ed)) {
                    case TeamTypes.BANDIT:
                        this.shipEntity = Blueprints.createFighter(ed, playerEntity);
                        break;
                    case TeamTypes.HOT_FUZZ:
                        this.shipEntity = Blueprints.createAssault(ed, playerEntity);
                        break;
                    default:
                        this.shipEntity = Blueprints.createAssault(ed, playerEntity);
                        break;
                }
            } else {

Where this is the most important part, after re-adding it I have also reset the shipid, even if it stays the same, that way I can trick it.

                getService(EtherealHost.class).setConnectionObject(conn, shipEntity.getId(), new Vec3d());
            }

            switch (team.getTypeName(ed)) {
                case TeamTypes.BANDIT:
                    ed.setComponent(shipEntity, new Position(0, 0, -30));
                    break;
                case TeamTypes.HOT_FUZZ:
                    ed.setComponent(shipEntity, new Position(0, 0, 30));
                    break;
                default:
                    ed.setComponent(shipEntity, new Position(0, 0, 30));
                    break;
            }

            ed.setComponents(shipEntity,
                    new TeamType(team.getType()),
                    new Control(new Vector3f(0, 0, 0)));

Just wanted to let you know. Maybe you see even a better way. But somehow this makes sense, at least to me.

1 Like