diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4e224a4..8d2a6b3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,6 +45,10 @@ jobs: env: DISPLAY: :99 GITHUB_WORKSPACE: $GITHUB_WORKSPACE + - name: start minecraft server + run: cd server && ./launch.sh & + env: + GITHUB_WORKSPACE: $GITHUB_WORKSPACE - name: checkout tagilmo uses: actions/checkout@v3 with: diff --git a/VERSION b/VERSION index c946ee6..1180819 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.6 +0.1.7 diff --git a/build.gradle b/build.gradle index f2b5efa..ff72923 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,20 @@ plugins { + id 'java' + id 'base' id 'fabric-loom' version '1.6-SNAPSHOT' id 'maven-publish' id("com.github.bjornvester.xjc") version "1.6.0" } -sourceCompatibility = JavaVersion.VERSION_21 -targetCompatibility = JavaVersion.VERSION_21 +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} version = rootProject.file('VERSION').text.trim() -archivesBaseName = project.archives_base_name + '-fabric_' + project.fabric_version +base { + archivesName = project.archives_base_name + '-fabric_' + project.fabric_version +} group = project.maven_group @@ -37,6 +43,7 @@ dependencies { implementation 'jakarta.annotation:jakarta.annotation-api:2.0.0' implementation 'jakarta.activation:jakarta.activation-api:2.0.1' implementation 'org.json:json:20230227' + compileOnly 'com.mojang:authlib' xjc "org.glassfish.jaxb:jaxb-runtime:3.0.2" xjc 'org.glassfish.jaxb:jaxb-xjc:3.0.1' @@ -80,6 +87,10 @@ jar { } jar.duplicatesStrategy = 'WARN' +loom { + accessWidenerPath = file("src/main/resources/vereya.accesswidener") +} + // configure the maven publication publishing { publications { diff --git a/config.xml b/config.xml index 1384725..2251982 100644 --- a/config.xml +++ b/config.xml @@ -15,5 +15,9 @@ + + + + diff --git a/fabric/fabric-server-mc.1.21-loader.0.16.5-launcher.1.0.1.jar b/fabric/fabric-server-mc.1.21-loader.0.16.5-launcher.1.0.1.jar new file mode 100644 index 0000000..9061c3c Binary files /dev/null and b/fabric/fabric-server-mc.1.21-loader.0.16.5-launcher.1.0.1.jar differ diff --git a/server/config.xml b/server/config.xml new file mode 120000 index 0000000..06352a5 --- /dev/null +++ b/server/config.xml @@ -0,0 +1 @@ +../config.xml \ No newline at end of file diff --git a/server/eula.txt b/server/eula.txt new file mode 100644 index 0000000..226bd2e --- /dev/null +++ b/server/eula.txt @@ -0,0 +1,3 @@ +#By changing the setting below to TRUE you are indicating your agreement to our EULA (https://aka.ms/MinecraftEULA). +#Thu Dec 29 16:39:29 GMT+04:00 2022 +eula=true diff --git a/server/launch.sh b/server/launch.sh new file mode 100755 index 0000000..7933261 --- /dev/null +++ b/server/launch.sh @@ -0,0 +1 @@ +java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8006 -Dlog4j.configurationFile=`pwd`/config.xml -Xmx2G -jar mods/fabric-server-mc.1.21-loader.0.16.5-launcher.1.0.1.jar --nogui diff --git a/server/server.properties b/server/server.properties new file mode 100644 index 0000000..c6ad89c --- /dev/null +++ b/server/server.properties @@ -0,0 +1,63 @@ +#Minecraft server properties +#Tue Oct 29 19:57:45 MSK 2024 +accepts-transfers=false +allow-flight=false +allow-nether=true +broadcast-console-to-ops=true +broadcast-rcon-to-ops=true +bug-report-link= +difficulty=easy +enable-command-block=false +enable-jmx-monitoring=false +enable-query=false +enable-rcon=false +enable-status=true +enforce-secure-profile=false +enforce-whitelist=false +entity-broadcast-range-percentage=100 +force-gamemode=false +function-permission-level=2 +gamemode=survival +generate-structures=true +generator-settings={"biome"\:"minecraft\:desert","layers"\:[{"block"\:"minecraft\:bedrock","height"\:1},{"block"\:"minecraft\:stone","height"\:3},{"block"\:"minecraft\:sandstone","height"\:116}],"structures"\:{"village"\:{}}} +hardcore=false +hide-online-players=false +initial-disabled-packs= +initial-enabled-packs=vanilla +level-name=world +level-seed=43839877843298 +level-type=minecraft\:normal +log-ips=true +max-chained-neighbor-updates=1000000 +max-players=20 +max-tick-time=60000 +max-world-size=29999984 +motd=A Minecraft Server +network-compression-threshold=256 +online-mode=false +op-permission-level=4 +player-idle-timeout=0 +prevent-proxy-connections=false +pvp=true +query.port=25565 +rate-limit=0 +rcon.password= +rcon.port=25575 +region-file-compression=deflate +require-resource-pack=false +resource-pack= +resource-pack-id= +resource-pack-prompt= +resource-pack-sha1= +server-ip= +server-port=25565 +simulation-distance=10 +spawn-animals=true +spawn-monsters=true +spawn-npcs=true +spawn-protection=16 +sync-chunk-writes=true +text-filtering-config= +use-native-transport=true +view-distance=10 +white-list=false diff --git a/src/main/java/io/singularitynet/Client/ClientStateMachine.java b/src/main/java/io/singularitynet/Client/ClientStateMachine.java index 5d7f95a..e0bb333 100755 --- a/src/main/java/io/singularitynet/Client/ClientStateMachine.java +++ b/src/main/java/io/singularitynet/Client/ClientStateMachine.java @@ -43,7 +43,12 @@ import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.GameMenuScreen; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.multiplayer.ConnectScreen; import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.network.ServerAddress; +import net.minecraft.client.network.ServerInfo; import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.Entity; import net.minecraft.entity.mob.MobEntity; @@ -550,6 +555,7 @@ public boolean onCommand(String command, String ipFrom, DataOutputStream dos) { LOGGER.info("Received from " + ipFrom + ":" + command.substring(0, Math.min(command.length(), 1024))); + boolean keepProcessing = false; // Possible commands: @@ -849,6 +855,7 @@ private void checkForMissionCommand() throws Exception { missionInit.getClientAgentConnection().setAgentIPAddress(comip.ipAddress); LOGGER.info("Mission received: " + missionInit.getMission().getAbout().getSummary()); + LOGGER.debug(missionMessage); csMachine.currentMissionInit = missionInit; ClientStateMachine.this.createMissionControlSocket(); // Move on to next state: @@ -1178,6 +1185,20 @@ protected void execute() { boolean needsNewWorld = worldGenerator != null && worldGenerator.shouldCreateWorld(currentMissionInit(), genOptions); boolean worldCurrentlyExists = world != null; + MinecraftServerConnection serverCon = currentMissionInit().getMinecraftServerConnection(); + LOGGER.debug("checking for server connection in mission init: ", serverCon); + if (serverCon != null && serverCon.getAddress() != null && serverCon.getPort() != 0) { + LOGGER.debug("server connection info is provided " + serverCon.toString() + + " assume world already exists"); + if (MinecraftClient.getInstance().isIntegratedServerRunning()){ + LOGGER.debug("stopping integrated server"); + MinecraftClient.getInstance().getServer().stop(true); + } + needsNewWorld = false; + worldCurrentlyExists = true; + episodeHasCompleted(ClientState.WAITING_FOR_SERVER_READY); + return; + } List agents = currentMissionInit().getMission().getAgentSection(); String agentName = agents.get(currentMissionInit().getClientRole()).getName(); if (worldCurrentlyExists) { @@ -1203,10 +1224,10 @@ protected void execute() { LOGGER.debug("needsNewWorld && worldCurrentlyExists"); episodeHasCompleted(ClientState.PAUSING_OLD_SERVER); } else { - // We want a new world, and there is currently nothing running, - // so jump to world creation: - LOGGER.debug("needsNewWorld && not worldCurrentlyExists"); - episodeHasCompleted(ClientState.CREATING_NEW_WORLD); + // We want a new world, and there is currently nothing running, + // so jump to world creation: + LOGGER.debug("needsNewWorld && not worldCurrentlyExists"); + episodeHasCompleted(ClientState.CREATING_NEW_WORLD); } } else { // not needNewWorld LOGGER.debug("not need new world"); @@ -1217,7 +1238,7 @@ protected void execute() { as.getAgentStart().setPlacement(null); ClientPlayerEntity player = MinecraftClient.getInstance().player; - if (player.isDead()) player.requestRespawn(); + if (player != null && player.isDead()) player.requestRespawn(); /* if (ClientStateMachine.this.serverHandlers == null) { // We need to use the server's MissionHandlers here: @@ -1234,6 +1255,7 @@ protected void execute() { // boolean isConnectedToRealm = MinecraftClient.getInstance().isConnectedToRealms(); boolean isConnectedToLocal = MinecraftClient.getInstance().isConnectedToLocalServer(); boolean isIntegratedServerRunning = MinecraftClient.getInstance().isIntegratedServerRunning(); + boolean isConnectedToRemote = !isIntegratedServerRunning && player != null; // LOGGER.debug("isConnectedToRealm: " + isConnectedToRealm); LOGGER.debug("isConnectedToLocal: " + isConnectedToLocal); LOGGER.debug("isIntegratedServerRunning: " + isIntegratedServerRunning); @@ -1258,21 +1280,8 @@ public void run() { } }); // Skip all the map loading stuff and go straight to waiting for the server: - episodeHasCompleted(ClientState.WAITING_FOR_SERVER_READY); - } else { - LOGGER.debug("sending mission to remote Server"); - HashMap map = new HashMap(); - // convert mission init with jaxb serializer - try { - String xmlData = SchemaHelper.serialiseObject(currentMissionInit(), MissionInit.class); - map.put("MissionInit", xmlData); - } catch (JAXBException e) { - episodeHasCompletedWithErrors(ClientState.ERROR_NO_WORLD, "exception while converting mission init to xml" + e.getMessage()); - } - // send mission init to server - ClientPlayNetworking.send(new MessagePayload(new VereyaMessage(VereyaMessageType.CLIENT_MISSION_INIT, 0, map))); - episodeHasCompleted(ClientState.WAITING_FOR_SERVER_READY); } + episodeHasCompleted(ClientState.WAITING_FOR_SERVER_READY); } else { // not needNewWorld and no world: error // Mission has requested no new world, but there is no current world to play in - this is an error: episodeHasCompletedWithErrors(ClientState.ERROR_NO_WORLD, "We have no world to play in - check that your ServerHandlers section contains a world generator"); @@ -1294,6 +1303,7 @@ public class WaitingForServerEpisode extends ErrorAwareEpisode implements IVerey boolean waitingForChunk = false; boolean waitingForPlayer = true; private boolean chunkReady = false; + private boolean sendToRemote = false; protected WaitingForServerEpisode(ClientStateMachine machine) { @@ -1388,8 +1398,9 @@ public void onClientTick(MinecraftClient client) if (agents.size() > 1 && currentMissionInit().getClientRole() != 0) { + /* throw new RuntimeException("Not implemented"); - /* + // We are waiting to join an out-of-process server. Need to pay attention to what happens - // if we can't join, for any reason, we should abort the mission. GuiScreen screen = Minecraft.getMinecraft().currentScreen; @@ -1413,8 +1424,7 @@ public void onClientTick(MinecraftClient client) } @Override - protected void execute() throws Exception - { + protected void execute() throws Exception { totalTicks = 0; // Minecraft.getMinecraft().displayGuiScreen(null); // Clear any menu screen that might confuse things. @@ -1423,7 +1433,23 @@ protected void execute() throws Exception //if (agents == null || agents.size() <= currentMissionInit().getClientRole()) // throw new Exception("No agent section for us!"); // TODO this.agentName = agents.get(currentMissionInit().getClientRole()).getName(); + MinecraftServerConnection serverCon = currentMissionInit().getMinecraftServerConnection(); + PlayerEntity player = MinecraftClient.getInstance().player; + boolean isConnectedToRemote = player != null && !MinecraftClient.getInstance().isIntegratedServerRunning(); + if (!isConnectedToRemote && serverCon != null && serverCon.getAddress() != null && serverCon.getPort() != 0) { + ServerAddress srv = new ServerAddress(serverCon.getAddress(), serverCon.getPort()); + LOGGER.debug("connecting to " + srv.getAddress() + ":" + srv.getPort()); + Screen parentScreen = new GameMenuScreen(true); + ServerInfo srvInfo = new ServerInfo("local", srv.getAddress(), ServerInfo.ServerType.LAN); + ConnectScreen.connect(parentScreen, MinecraftClient.getInstance(), srv, srvInfo, true, null); + this.sendToRemote = true; + } + + if (isConnectedToRemote && currentMissionInit().getClientRole() == 0) { + this.sendToRemote = true; + } + /* if (agents.size() > 1 && currentMissionInit().getClientRole() != 0) { // Multi-agent mission, we should be joining a server. @@ -1445,11 +1471,25 @@ protected void execute() throws Exception } this.waitingForPlayer = false; LOGGER.info("player exists and names match"); - } + }*/ } protected void handleLan() { + if (this.sendToRemote) { + LOGGER.debug("sending mission to remote Server"); + HashMap map = new HashMap(); + // convert mission init with jaxb serializer + try { + String xmlData = SchemaHelper.serialiseObject(currentMissionInit(), MissionInit.class); + map.put("MissionInit", xmlData); + } catch (JAXBException e) { + episodeHasCompletedWithErrors(ClientState.ERROR_NO_WORLD, "exception while converting mission init to xml" + e.getMessage()); + } + // send mission init to server + ClientPlayNetworking.send(new MessagePayload(new VereyaMessage(VereyaMessageType.CLIENT_MISSION_INIT, 0, map))); + sendToRemote = false; + } // Get our name from the Mission: /*List agents = currentMissionInit().getMission().getAgentSection(); this.agentName = agents.get(currentMissionInit().getClientRole()).getName(); diff --git a/src/main/java/io/singularitynet/Client/VereyaModClient.java b/src/main/java/io/singularitynet/Client/VereyaModClient.java index 5de3af9..6e2b8d0 100644 --- a/src/main/java/io/singularitynet/Client/VereyaModClient.java +++ b/src/main/java/io/singularitynet/Client/VereyaModClient.java @@ -149,11 +149,12 @@ public void onKey(long window, int key, int scancode, int action, int modifiers) @Override public void onInitializeClient() { + LOGGER.info("initialising vereya mod client"); this.stateMachine = new ClientStateMachine(ClientState.WAITING_FOR_MOD_READY, (IMalmoModClient) this); // subscribe to setScreen event ScreenEvents.SET_SCREEN.register(this); - PayloadTypeRegistry.playS2C().register(MessagePayload.ID, MessagePayload.CODEC); - // register the instance for messages from Server to the Client + // this is registered in VereyaModServer even on client + // PayloadTypeRegistry.playS2C().register(MessagePayload.ID, MessagePayload.CODEC); ClientPlayNetworking.registerGlobalReceiver(MessagePayload.ID, (payload, context) -> { SidesMessageHandler.server2client.onMessage(payload, context) ; }); } diff --git a/src/main/java/io/singularitynet/Server/ServerStateMachine.java b/src/main/java/io/singularitynet/Server/ServerStateMachine.java index df00e71..8efcd40 100644 --- a/src/main/java/io/singularitynet/Server/ServerStateMachine.java +++ b/src/main/java/io/singularitynet/Server/ServerStateMachine.java @@ -27,12 +27,14 @@ import jakarta.xml.bind.JAXBElement; import jakarta.xml.bind.JAXBException; +import net.fabricmc.fabric.mixin.event.lifecycle.*; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.player.PlayerEntity; @@ -77,6 +79,7 @@ public class ServerStateMachine extends StateMachine implements IVereyaMessageLi private ArrayList userConnectionWatchList = new ArrayList(); private ArrayList userTurnSchedule = new ArrayList(); public Map controllableEntities = new HashMap(); + protected Set mobsInGame; /** Called to initialise a state machine for a specific Mission request.
* Most likely caused by the client creating an integrated server. @@ -93,6 +96,7 @@ public ServerStateMachine(ServerState initialState, MissionInit minit, Minecraft LOGGER.debug("ServerStateMachine: " + this + " server " + server); this.server = new WeakReference(server); + this.mobsInGame = new HashSet(); // Register ourself on the event busses, so we can harness the server tick: ServerTickEvents.END_SERVER_TICK.register(this::onServerTick); ServerLifecycleEvents.SERVER_STOPPING.register(this::onServerStopping); @@ -111,6 +115,7 @@ private void onEntityUnload(Entity entity, ServerWorld serverWorld) { LOGGER.debug("removing controlled mob uuid: " + uuid); controllableEntities.remove(uuid); } + if (this.mobsInGame.contains(entity)) this.mobsInGame.remove(entity); } } @@ -122,12 +127,12 @@ private void onEntityLoad(Entity entity, ServerWorld serverWorld) { controllableEntities.put(uuid, mobEntity); LOGGER.debug("sending new controlled mob message to client uuid: " + uuid); sendToAll(new VereyaMessage(VereyaMessageType.SERVER_CONTROLLED_MOB, uuid)); + } else { + this.mobsInGame.add((MobEntity) entity); } } } - - /** Used to prevent spawning in our world.*/ public ActionResult onGetPotentialSpawns(Entity entity, ServerWorld world) { @@ -144,7 +149,6 @@ public ActionResult onGetPotentialSpawns(Entity entity, ServerWorld world) ServerInitialConditions sic = (ss != null) ? ss.getServerInitialConditions() : null; if (sic != null) allowSpawning = (sic.isAllowSpawning() == Boolean.TRUE); - if (allowSpawning && sic.getAllowedMobs() != null && !sic.getAllowedMobs().isEmpty()) { // Spawning is allowed, but restricted to our list: @@ -285,11 +289,13 @@ private void sendToAll(VereyaMessage msg){ return; } for(ServerPlayerEntity player: server.getPlayerManager().getPlayerList()){ + LOGGER.debug("send " + msg.getMessageType().toString() + " to " + player.getName()); ServerPlayNetworking.send(player, new MessagePayload(msg)); } } private void sendToPlayer(VereyaMessage msg, ServerPlayerEntity player){ + LOGGER.debug("send " + msg.getMessageType().toString() + " to " + player.getName()); ServerPlayNetworking.send(player, new MessagePayload(msg)); } @@ -1032,7 +1038,6 @@ private void initialiseEnderInventory(PlayerEntity player, AgentStart.EnderBoxIn } } - //--------------------------------------------------------------------------------------------------------- /** Mission running state. */ diff --git a/src/main/java/io/singularitynet/Server/VereyaModServer.java b/src/main/java/io/singularitynet/Server/VereyaModServer.java index d924643..ad313e7 100644 --- a/src/main/java/io/singularitynet/Server/VereyaModServer.java +++ b/src/main/java/io/singularitynet/Server/VereyaModServer.java @@ -55,15 +55,21 @@ public boolean hasServer(){ @Override public void onInitialize() { + Logger LOGGER = LogManager.getLogger(); + LOGGER.info("initialising vereya mod server"); instance = this; PayloadTypeRegistry.playC2S().register(MessagePayload.ID, MessagePayload.CODEC); + PayloadTypeRegistry.playS2C().register(MessagePayload.ID, MessagePayload.CODEC); // register the instance for messages from Client to the Server ServerPlayNetworking.registerGlobalReceiver(MessagePayload.ID, (payload, context) -> { SidesMessageHandler.client2server.onMessage(payload, context); }); ServerLifecycleEvents.SERVER_STARTED.register((MinecraftServer server) -> { - Logger LOGGER = LogManager.getLogger(); - LOGGER.info("Server started"); + String msg = "integrated "; + if (server.isDedicated()){ + msg = "dedicated "; + } + LOGGER.info(msg + "Server started"); if (stateMachine == null ) { this.stateMachine = new ServerStateMachine(ServerState.WAITING_FOR_MOD_READY, null, server); } else { diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index d9e74d8..5291df1 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -9,8 +9,8 @@ "Anatoly Belikov" ], "contact": { - "homepage": "https://fabricmc.net/", - "sources": "https://github.com/FabricMC/fabric-example-mod" + "homepage": "https://github.com/trueagi-io/Vereya", + "sources": "https://github.com/trueagi-io/Vereya" }, "license": "CC0-1.0", diff --git a/src/main/resources/vereya.accesswidener b/src/main/resources/vereya.accesswidener new file mode 100644 index 0000000..e823f6b --- /dev/null +++ b/src/main/resources/vereya.accesswidener @@ -0,0 +1,5 @@ +accessWidener v2 named + +accessible class net/minecraft/client/network/ClientLoginNetworkHandler$State +accessible field net/minecraft/client/network/ClientLoginNetworkHandler$State name Lnet/minecraft/text/Text; +accessible field net/minecraft/client/network/ClientLoginNetworkHandler$State prevStates Ljava/util/Set; \ No newline at end of file diff --git a/src/main/resources/vereya.mixins.json b/src/main/resources/vereya.mixins.json index c2c6d03..e73ef26 100644 --- a/src/main/resources/vereya.mixins.json +++ b/src/main/resources/vereya.mixins.json @@ -6,12 +6,11 @@ "mixins": [], "client": [ "TitleScreenMixin", "SessionMixin", "MinecraftClientMixin", "GameOptionsMixin", - "KeyBindingMixin", "MouseMixin", "MouseAccessorMixin", "InGameHudMixin", "ServerWorldEntityLoaderMixin", - "ServerEntityManagerMixin", "ServerWorldMixin", "LevelStorageMixin", + "KeyBindingMixin", "MouseMixin", "MouseAccessorMixin", "InGameHudMixin", "LevelStorageMixin", "MinecraftClientEventsMixin", "LootTableProviderMixin", "MobEntityMixin", "MobEntityAccessorMixin", - "ClientWorldMixinAccess", "EntityMixin" + "ClientWorldMixinAccess", "EntityMixin", "ServerEntityManagerMixin", "ServerWorldMixin", "ServerWorldEntityLoaderMixin" ], - "server": [], + "server": ["ServerEntityManagerMixin", "ServerWorldMixin", "ServerWorldEntityLoaderMixin"], "injectors": { "defaultRequire": 1 }