├── .gitignore ├── .idea ├── .gitignore ├── artifacts │ └── Multiplayer.xml ├── compiler.xml ├── jarRepositories.xml ├── libraries │ ├── CMUtils.xml │ ├── LazyLib_Kotlin.xml │ ├── commons_compiler_jdk.xml │ ├── lw_Console.xml │ └── netty_buffer_4_1_69_Final.xml ├── misc.xml ├── modules.xml ├── runConfigurations │ └── Starsector.xml ├── uiDesigner.xml └── vcs.xml ├── Multiplayer.iml ├── data ├── config │ ├── mp │ │ └── proj_spawns.csv │ └── settings.json ├── console │ └── commands.csv ├── missions │ ├── MP_Default │ │ ├── descriptor.json │ │ ├── logo.png │ │ └── mission_text.txt │ ├── MP_FighterTest │ │ ├── descriptor.json │ │ ├── logo.png │ │ └── mission_text.txt │ └── mission_list.csv └── weapons │ ├── mp_hurricane_proj_spawn.wpn │ ├── mp_pilum_proj_spawn.wpn │ ├── mp_sabot_proj_spawn.wpn │ └── weapon_data.csv ├── jars ├── Multiplayer.jar └── netty │ ├── netty-all-4.1.69.Final.jar │ ├── netty-buffer-4.1.69.Final.jar │ ├── netty-codec-4.1.69.Final.jar │ ├── netty-codec-dns-4.1.69.Final.jar │ ├── netty-common-4.1.69.Final.jar │ ├── netty-resolver-4.1.69.Final.jar │ ├── netty-resolver-dns-4.1.69.Final.jar │ └── netty-transport-4.1.69.Final.jar ├── mod_info.json ├── runcodes.txt ├── src └── data │ ├── missions │ ├── MP_Default │ │ ├── MissionDefinition.java │ │ └── MultiplayerMissionPlugin.java │ └── MP_FighterTest │ │ └── MissionDefinition.java │ └── scripts │ ├── MPModPlugin.java │ ├── console │ └── commands │ │ ├── mpConnectToHost.java │ │ ├── mpConnectToHostCached.java │ │ ├── mpFlush.java │ │ ├── mpPilotShip.java │ │ └── mpRunServer.java │ ├── misc │ └── MapSet.java │ ├── net │ ├── data │ │ ├── DataGenManager.java │ │ ├── InboundData.java │ │ ├── InstanceData.java │ │ ├── OutboundData.java │ │ ├── datagen │ │ │ ├── BaseDatagen.java │ │ │ ├── FighterVariantDatastore.java │ │ │ ├── ProjectileSpecDatastore.java │ │ │ └── ShipVariantDatastore.java │ │ ├── packables │ │ │ ├── DestExecute.java │ │ │ ├── EntityData.java │ │ │ ├── InterpExecute.java │ │ │ ├── InterpRecordLambda.java │ │ │ ├── RecordLambda.java │ │ │ ├── SourceExecute.java │ │ │ ├── entities │ │ │ │ ├── projectiles │ │ │ │ │ ├── BallisticProjectileData.java │ │ │ │ │ ├── MissileData.java │ │ │ │ │ └── MovingRayData.java │ │ │ │ └── ships │ │ │ │ │ ├── ClientPlayerData.java │ │ │ │ │ ├── ShieldData.java │ │ │ │ │ ├── ShipData.java │ │ │ │ │ ├── VariantData.java │ │ │ │ │ └── WeaponData.java │ │ │ └── metadata │ │ │ │ ├── ChatListenData.java │ │ │ │ ├── ClientConnectionData.java │ │ │ │ ├── ClientData.java │ │ │ │ ├── LobbyData.java │ │ │ │ ├── ServerConnectionData.java │ │ │ │ └── ServerPlayerData.java │ │ ├── plugins │ │ │ ├── ClientScript.java │ │ │ ├── DEMPlugin.java │ │ │ └── ServerScript.java │ │ ├── records │ │ │ ├── ByteRecord.java │ │ │ ├── ConversionUtils.java │ │ │ ├── DataRecord.java │ │ │ ├── Float16Record.java │ │ │ ├── Float32Record.java │ │ │ ├── IntRecord.java │ │ │ ├── ShortRecord.java │ │ │ ├── StringRecord.java │ │ │ ├── Vector2f16Record.java │ │ │ ├── Vector2f32Record.java │ │ │ ├── Vector3f32Record.java │ │ │ └── collections │ │ │ │ ├── ListenArrayRecord.java │ │ │ │ ├── SyncingListRecord.java │ │ │ │ └── SyncingMapRecord.java │ │ └── tables │ │ │ ├── BaseEntityManager.java │ │ │ ├── EntityInstanceMap.java │ │ │ ├── EntityTable.java │ │ │ ├── InboundEntityManager.java │ │ │ ├── OutboundEntityManager.java │ │ │ ├── client │ │ │ ├── ClientCustomDataTable.java │ │ │ └── combat │ │ │ │ ├── connection │ │ │ │ ├── LobbyInput.java │ │ │ │ └── TextChatClient.java │ │ │ │ ├── entities │ │ │ │ ├── ClientProjectileTable.java │ │ │ │ ├── VariantDataMap.java │ │ │ │ └── ships │ │ │ │ │ └── ClientShipTable.java │ │ │ │ └── player │ │ │ │ ├── Player.java │ │ │ │ └── PlayerShip.java │ │ │ └── server │ │ │ ├── ServerCustomDataTable.java │ │ │ └── combat │ │ │ ├── connection │ │ │ └── TextChatHost.java │ │ │ ├── entities │ │ │ ├── DamagingExplosionTable.java │ │ │ ├── ProjectileTable.java │ │ │ └── ships │ │ │ │ └── ShipTable.java │ │ │ └── players │ │ │ ├── PlayerLobby.java │ │ │ └── PlayerShips.java │ └── io │ │ ├── BaseConnectionWrapper.java │ │ ├── ByteArrayReader.java │ │ ├── ClientConnectionWrapper.java │ │ ├── ClientDuplex.java │ │ ├── Clock.java │ │ ├── CompressionUtils.java │ │ ├── MessageContainer.java │ │ ├── ServerConnectionManager.java │ │ ├── ServerConnectionWrapper.java │ │ ├── ServerDuplex.java │ │ ├── Unpacked.java │ │ ├── tcp │ │ ├── BufferUnpacker.java │ │ ├── MessageContainerDecoder.java │ │ ├── MessageContainerEncoder.java │ │ ├── client │ │ │ ├── ClientChannelHandler.java │ │ │ └── SocketClient.java │ │ └── server │ │ │ ├── ServerChannelHandler.java │ │ │ ├── SocketChannelInitializer.java │ │ │ └── SocketServer.java │ │ └── udp │ │ ├── DatagramDecoder.java │ │ ├── DatagramUnpacker.java │ │ ├── DatagramUtils.java │ │ ├── client │ │ ├── ClientInboundHandler.java │ │ └── DatagramClient.java │ │ └── server │ │ ├── DatagramChannelInitializer.java │ │ ├── DatagramServer.java │ │ └── ServerInboundHandler.java │ ├── plugins │ ├── MPClientPlugin.java │ ├── MPLogger.java │ ├── MPPlugin.java │ ├── MPServerPlugin.java │ ├── ai │ │ ├── MPDefaultAutofireAIPlugin.java │ │ ├── MPDefaultMissileAIPlugin.java │ │ └── MPDefaultShipAIPlugin.java │ └── gui │ │ ├── MPChatboxPlugin.java │ │ └── MPUIPlugin.java │ └── test │ └── MPTest.java └── target └── classes └── data └── scripts └── console └── commands └── mpRunServer.class /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/artifacts/Multiplayer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/jars 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/libraries/CMUtils.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/LazyLib_Kotlin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/libraries/commons_compiler_jdk.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/libraries/lw_Console.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/libraries/netty_buffer_4_1_69_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Starsector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Multiplayer.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /data/config/mp/proj_spawns.csv: -------------------------------------------------------------------------------- 1 | projectileID,weaponID 2 | sabot_warhead2,mp_sabot_proj_spawn 3 | mirv_warhead,mp_hurricane_proj_spawn 4 | pilum_second_stage,mp_pilum_proj_spawn -------------------------------------------------------------------------------- /data/config/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins" : { 3 | "MPUIPlugin": "data.scripts.plugins.gui.MPUIPlugin", 4 | }, 5 | "mpServerTickRate": 60, # rate at which server broadcasts packets 6 | "mpClientTickrate": 30, # rate at which client will send command packets 7 | "mpMaxConnections": 4, 8 | 9 | "mpSocketQueueLimit": 32, 10 | "mpDatagramQueueLimit": 64, 11 | 12 | "MP_PacketSize": 600, 13 | "MP_UsernameString": "tomatopaste" #max 12 characters 14 | } -------------------------------------------------------------------------------- /data/console/commands.csv: -------------------------------------------------------------------------------- 1 | command,class,tags,syntax,help 2 | mpP,data.scripts.console.commands.mpPilotShip,"combat,multiplayer",mpPilotShip,Pilot nearest ship -------------------------------------------------------------------------------- /data/missions/MP_Default/descriptor.json: -------------------------------------------------------------------------------- 1 | { 2 | "title":"Multiplayer", 3 | "difficulty":"GOOD", 4 | "icon":"logo.png", 5 | "background":"graphics/backgrounds/background5.jpg" 6 | } -------------------------------------------------------------------------------- /data/missions/MP_Default/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automatopaste/Multiplayer/6ea94d27b83589eb86c56b5fd51910776b51f6c3/data/missions/MP_Default/logo.png -------------------------------------------------------------------------------- /data/missions/MP_Default/mission_text.txt: -------------------------------------------------------------------------------- 1 | And after all we're playing multiplayer -------------------------------------------------------------------------------- /data/missions/MP_FighterTest/descriptor.json: -------------------------------------------------------------------------------- 1 | { 2 | "title":"Fighter Test", 3 | "difficulty":"GOOD", 4 | "icon":"logo.png", 5 | "background":"graphics/backgrounds/background5.jpg" 6 | } -------------------------------------------------------------------------------- /data/missions/MP_FighterTest/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automatopaste/Multiplayer/6ea94d27b83589eb86c56b5fd51910776b51f6c3/data/missions/MP_FighterTest/logo.png -------------------------------------------------------------------------------- /data/missions/MP_FighterTest/mission_text.txt: -------------------------------------------------------------------------------- 1 | Lmao -------------------------------------------------------------------------------- /data/missions/mission_list.csv: -------------------------------------------------------------------------------- 1 | mission 2 | MP_Default 3 | MP_FighterTest -------------------------------------------------------------------------------- /data/weapons/mp_hurricane_proj_spawn.wpn: -------------------------------------------------------------------------------- 1 | { 2 | "id":"mp_hurricane_proj_spawn", 3 | "specClass":"projectile", 4 | "type":"MISSILE", 5 | "size":"LARGE", 6 | "turretSprite":"graphics/weapons/hurricane_mirv_launcher_turret_base.png", 7 | "hardpointSprite":"graphics/weapons/hurricane_mirv_launcher_hardpoint_base.png", 8 | #"hardpointOffsets":[17, -5, 17, 5], 9 | #"turretOffsets":[13, -5, 13, 5], 10 | #"hardpointAngleOffsets":[0, 0], 11 | #"turretAngleOffsets":[0, 0], 12 | "hardpointOffsets":[23, 0], 13 | "turretOffsets":[17, 0], 14 | "hardpointAngleOffsets":[0], 15 | "turretAngleOffsets":[0], 16 | "barrelMode":"ALTERNATING", 17 | "animationType":"SMOKE", 18 | "renderHints":[RENDER_LOADED_MISSILES], 19 | "interruptibleBurst":false, 20 | "displayArcRadius":300, 21 | "smokeSpec":{"particleSizeMin":20.0, 22 | "particleSizeRange":20.0, 23 | "cloudParticleCount":3, 24 | "cloudDuration":1.0, 25 | "cloudRadius":10.0, 26 | "blowbackParticleCount":3, 27 | "blowbackDuration":2.0, 28 | "blowbackLength":30.0, 29 | "blowbackSpread":10.0, 30 | "particleColor":[100,100,100,200]}, 31 | "projectileSpecId":"mirv_warhead", 32 | "fireSoundTwo":"hurricane_mirv_fire", 33 | } -------------------------------------------------------------------------------- /data/weapons/mp_pilum_proj_spawn.wpn: -------------------------------------------------------------------------------- 1 | { 2 | "id":"mp_pilum_proj_spawn", 3 | "specClass":"projectile", 4 | "type":"MISSILE", 5 | "size":"MEDIUM", 6 | "turretSprite":"graphics/weapons/pilum_lrm_launcher_turret_base.png", 7 | "hardpointSprite":"graphics/weapons/pilum_lrm_launcher_hardpoint_base.png", 8 | "hardpointOffsets":[20, -6, 20, 0, 20, 6], 9 | "turretOffsets":[15, -6, 15, 0, 15, 6], 10 | "hardpointAngleOffsets":[0, 0, 0], 11 | "turretAngleOffsets":[0, 0, 0], 12 | "barrelMode":"ALTERNATING", 13 | "animationType":"SMOKE", 14 | "renderHints":[RENDER_LOADED_MISSILES], 15 | "interruptibleBurst":false, 16 | "smokeSpec":{"particleSizeMin":20.0, 17 | "particleSizeRange":20.0, 18 | "cloudParticleCount":3, 19 | "cloudDuration":1.0, 20 | "cloudRadius":10.0, 21 | "blowbackParticleCount":3, 22 | "blowbackDuration":2.0, 23 | "blowbackLength":30.0, 24 | "blowbackSpread":10.0, 25 | "particleColor":[100,100,100,200]}, 26 | "projectileSpecId":"pilum_second_stage", 27 | "fireSoundTwo":"pilum_lrm_fire", 28 | } -------------------------------------------------------------------------------- /data/weapons/mp_sabot_proj_spawn.wpn: -------------------------------------------------------------------------------- 1 | { 2 | "id":"mp_sabot_proj_spawn", 3 | "specClass":"projectile", 4 | "type":"MISSILE", 5 | "size":"SMALL", 6 | "turretSprite":"graphics/weapons/missile_rack_m_3x_turret_base.png", 7 | "hardpointSprite":"graphics/weapons/missile_rack_m_3x_hardpoint_base.png", 8 | "hardpointOffsets":[15, -6, 15, 6, 15, 0], 9 | "turretOffsets":[8, -6, 8, 6, 8, 0], 10 | "hardpointAngleOffsets":[0, 0, 0], 11 | "turretAngleOffsets":[0, 0, 0], 12 | "barrelMode":"ALTERNATING", 13 | "animationType":"SMOKE", 14 | "renderHints":[RENDER_LOADED_MISSILES], 15 | "interruptibleBurst":false, 16 | "displayArcRadius":300, 17 | "smokeSpec":{"particleSizeMin":20.0, 18 | "particleSizeRange":20.0, 19 | "cloudParticleCount":3, 20 | "cloudDuration":1.0, 21 | "cloudRadius":10.0, 22 | "blowbackParticleCount":3, 23 | "blowbackDuration":2.0, 24 | "blowbackLength":30.0, 25 | "blowbackSpread":10.0, 26 | "particleColor":[100,100,100,200]}, 27 | "projectileSpecId":"sabot_warhead2", 28 | "fireSoundTwo":"sabot_srm_fire", 29 | } -------------------------------------------------------------------------------- /data/weapons/weapon_data.csv: -------------------------------------------------------------------------------- 1 | name,id,tier,rarity,base value,range,damage/second,damage/shot,emp,impact,turn rate,OPs,ammo,ammo/sec,reload size,type,energy/shot,energy/second,chargeup,chargedown,burst size,burst delay,min spread,max spread,spread/shot,spread decay/sec,beam speed,proj speed,launch speed,flight time,proj hitpoints,autofireAccBonus,extraArcForAI,hints,tags,groupTag,tech/manufacturer,for weapon tooltip>>,primaryRoleStr,speedStr,trackingStr,turnRateStr,accuracyStr,customPrimary,customPrimaryHL,customAncillary,customAncillaryHL,noDPSInTooltip,number 2 | Sabot MIRV Spawn,mp_sabot_proj_spawn,1,,0,600,,100,200,,,4,3,,,KINETIC,0,,0,1,1,,0,0,0,0,,1000,,,500,,,SYSTEM,,,,,Anti Shield,Slow,Poor,,,"Submunition hits on hull or armor have a %s chance to arc to weapons and engines, dealing %s EMP damage.",25% | 200,,,TRUE,11.75 3 | Hurricane MIRV Spawn,mp_hurricane_proj_spawn,1,,0,2500,,500,,5,,25,10,,,HIGH_EXPLOSIVE,0,,0,15,1,,0,0,0,0,,250,50,12,250,,,SYSTEM,,,,,Finisher,Medium,Good,,,,,,,TRUE,16 4 | Pilum MIRV Spawn,mp_pilum_proj_spawn,0,,0,4000,,500,500,25,10,7,30,0.1,,FRAGMENTATION,0,,0,15,3,0.4,0,0,0,0,,125,100,30,150,,,SYSTEM,,,,,Long Range Support,Very Slow,Very Poor,,,"Second-stage hits on shields have a chance to arc through to weapons and engines, dealing %s EMP damage. The chance to pierce the shields is based on the target's hard flux level.",500,,,TRUE,12.5 -------------------------------------------------------------------------------- /jars/Multiplayer.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automatopaste/Multiplayer/6ea94d27b83589eb86c56b5fd51910776b51f6c3/jars/Multiplayer.jar -------------------------------------------------------------------------------- /jars/netty/netty-all-4.1.69.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automatopaste/Multiplayer/6ea94d27b83589eb86c56b5fd51910776b51f6c3/jars/netty/netty-all-4.1.69.Final.jar -------------------------------------------------------------------------------- /jars/netty/netty-buffer-4.1.69.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automatopaste/Multiplayer/6ea94d27b83589eb86c56b5fd51910776b51f6c3/jars/netty/netty-buffer-4.1.69.Final.jar -------------------------------------------------------------------------------- /jars/netty/netty-codec-4.1.69.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automatopaste/Multiplayer/6ea94d27b83589eb86c56b5fd51910776b51f6c3/jars/netty/netty-codec-4.1.69.Final.jar -------------------------------------------------------------------------------- /jars/netty/netty-codec-dns-4.1.69.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automatopaste/Multiplayer/6ea94d27b83589eb86c56b5fd51910776b51f6c3/jars/netty/netty-codec-dns-4.1.69.Final.jar -------------------------------------------------------------------------------- /jars/netty/netty-common-4.1.69.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automatopaste/Multiplayer/6ea94d27b83589eb86c56b5fd51910776b51f6c3/jars/netty/netty-common-4.1.69.Final.jar -------------------------------------------------------------------------------- /jars/netty/netty-resolver-4.1.69.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automatopaste/Multiplayer/6ea94d27b83589eb86c56b5fd51910776b51f6c3/jars/netty/netty-resolver-4.1.69.Final.jar -------------------------------------------------------------------------------- /jars/netty/netty-resolver-dns-4.1.69.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automatopaste/Multiplayer/6ea94d27b83589eb86c56b5fd51910776b51f6c3/jars/netty/netty-resolver-dns-4.1.69.Final.jar -------------------------------------------------------------------------------- /jars/netty/netty-transport-4.1.69.Final.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automatopaste/Multiplayer/6ea94d27b83589eb86c56b5fd51910776b51f6c3/jars/netty/netty-transport-4.1.69.Final.jar -------------------------------------------------------------------------------- /mod_info.json: -------------------------------------------------------------------------------- 1 | { 2 | "id":"multiplayer", 3 | "name":"Multiplayer", 4 | "author":"tomatopaste", 5 | "version":"0.1.2", 6 | "description":"Combat multiplayer mod.", 7 | "gameVersion":"0.97a-RC8", 8 | "jars":[ 9 | "jars/Multiplayer.jar", 10 | "jars/netty/netty-all-4.1.69.Final.jar", 11 | "jars/netty/netty-buffer-4.1.69.Final.jar", 12 | "jars/netty/netty-codec-4.1.69.Final.jar", 13 | "jars/netty/netty-codec-dns-4.1.69.Final.jar", 14 | "jars/netty/netty-common-4.1.69.Final.jar", 15 | "jars/netty/netty-transport-4.1.69.Final.jar", 16 | "jars/netty/netty-resolver-4.1.69.Final.jar", 17 | "jars/netty/netty-resolver-dns-4.1.69.Final.jar" 18 | ], 19 | "modPlugin":"data.scripts.MPModPlugin", 20 | "utility": true 21 | } -------------------------------------------------------------------------------- /runcodes.txt: -------------------------------------------------------------------------------- 1 | -disable all ai 2 | runcode for (ShipAPI ship : Global.getCombatEngine().getShips()) { 3 | ship.setShipAI(new data.scripts.plugins.ai.MPDefaultShipAIPlugin());} -------------------------------------------------------------------------------- /src/data/missions/MP_Default/MissionDefinition.java: -------------------------------------------------------------------------------- 1 | package data.missions.MP_Default; 2 | 3 | import com.fs.starfarer.api.fleet.FleetGoal; 4 | import com.fs.starfarer.api.fleet.FleetMemberType; 5 | import com.fs.starfarer.api.mission.FleetSide; 6 | import com.fs.starfarer.api.mission.MissionDefinitionAPI; 7 | import com.fs.starfarer.api.mission.MissionDefinitionPlugin; 8 | 9 | public class MissionDefinition implements MissionDefinitionPlugin { 10 | 11 | private static final int NUM_PLAYER = 6; 12 | private static final int NUM_ENEMY = 6; 13 | 14 | @Override 15 | public void defineMission(MissionDefinitionAPI api) { 16 | api.addPlanet(0f, 0f, 150f, "barren-bombarded", 0f, true); 17 | 18 | api.initFleet(FleetSide.PLAYER, "PCS", FleetGoal.ATTACK, false); 19 | api.initFleet(FleetSide.ENEMY, "TTS", FleetGoal.ATTACK, true); 20 | 21 | api.setFleetTagline(FleetSide.PLAYER, "Us"); 22 | api.setFleetTagline(FleetSide.ENEMY, "Them"); 23 | 24 | api.addToFleet(FleetSide.PLAYER, "hyperion_Attack", FleetMemberType.SHIP, true); 25 | api.addToFleet(FleetSide.ENEMY, "hyperion_Attack", FleetMemberType.SHIP, true); 26 | 27 | // Set up the map. 28 | float width = 15000f; 29 | float height = 15000f; 30 | api.initMap(-width / 2f, width / 2f, -height / 2f, height / 2f); 31 | 32 | float minX = -width / 2; 33 | float minY = -height / 2; 34 | 35 | for (int i = 0; i < 50; i++) { 36 | float x = (float) Math.random() * width - width / 2; 37 | float y = (float) Math.random() * height - height / 2; 38 | float radius = 100f + (float) Math.random() * 400f; 39 | api.addNebula(x, y, radius); 40 | } 41 | 42 | // Add objectives 43 | api.addObjective(minX + width * 0.25f, minY + height * 0.25f, "nav_buoy"); 44 | api.addObjective(minX + width * 0.35f, minY + height * 0.65f, "comm_relay"); 45 | api.addObjective(minX + width * 0.75f, minY + height * 0.45f, "nav_buoy"); 46 | api.addObjective(minX + width * 0.65f, minY + height * 0.35f, "comm_relay"); 47 | api.addObjective(minX + width * 0.5f, minY + height * 0.25f, "sensor_array"); 48 | 49 | api.addPlugin(new MultiplayerMissionPlugin()); 50 | } 51 | } -------------------------------------------------------------------------------- /src/data/missions/MP_Default/MultiplayerMissionPlugin.java: -------------------------------------------------------------------------------- 1 | package data.missions.MP_Default; 2 | 3 | import com.fs.starfarer.api.Global; 4 | import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin; 5 | import com.fs.starfarer.api.combat.CombatEngineAPI; 6 | import com.fs.starfarer.api.combat.CombatFleetManagerAPI; 7 | import com.fs.starfarer.api.combat.ShipAPI; 8 | import com.fs.starfarer.api.fleet.FleetMemberAPI; 9 | import com.fs.starfarer.api.fleet.FleetMemberType; 10 | import com.fs.starfarer.api.input.InputEventAPI; 11 | import com.fs.starfarer.api.mission.FleetSide; 12 | import com.fs.starfarer.api.util.IntervalUtil; 13 | import data.scripts.MPModPlugin; 14 | import data.scripts.net.data.packables.entities.ships.ClientPlayerData; 15 | import data.scripts.net.data.packables.entities.ships.ShipData; 16 | import data.scripts.net.data.tables.server.combat.players.PlayerShips; 17 | import data.scripts.plugins.MPPlugin; 18 | import data.scripts.plugins.MPServerPlugin; 19 | import org.lwjgl.util.vector.Vector2f; 20 | 21 | import java.util.ArrayList; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | public class MultiplayerMissionPlugin extends BaseEveryFrameCombatPlugin { 27 | 28 | private static final List variants = new ArrayList<>(); 29 | static { 30 | variants.add("hyperion_Attack"); 31 | } 32 | 33 | private final Map cooldowns = new HashMap<>(); 34 | 35 | @Override 36 | public void advance(float amount, List events) { 37 | CombatEngineAPI engine = Global.getCombatEngine(); 38 | 39 | engine.setDoNotEndCombat(true); 40 | 41 | if (engine.isPaused()) return; 42 | 43 | MPPlugin plugin = MPModPlugin.getPlugin(); 44 | if (!(plugin instanceof MPServerPlugin)) return; 45 | MPServerPlugin server = (MPServerPlugin) plugin; 46 | 47 | PlayerShips playerShips = (PlayerShips) server.getEntityManagers().get(PlayerShips.class); 48 | Map controlData = playerShips.getControlData(); 49 | 50 | //runcode Global.getCombatEngine().applyDamage(Global.getCombatEngine().getPlayerShip(), Global.getCombatEngine().getPlayerShip().getLocation(), 9999999f, DamageType.ENERGY, 0f, true, false, null); 51 | 52 | // host 53 | short hostID = playerShips.getHostShipID(); 54 | ShipAPI host = null; 55 | if (hostID != PlayerShips.NULL_SHIP_ID) { 56 | ShipData shipData = playerShips.getShipTable().getShipTable().array()[hostID]; 57 | 58 | if (shipData != null) { 59 | host = shipData.getShip(); 60 | } 61 | } 62 | 63 | IntervalUtil hostCooldown = cooldowns.get((short)-1); 64 | if (hostCooldown == null) { 65 | hostCooldown = new IntervalUtil(1f, 1f); 66 | cooldowns.put((short)-1, hostCooldown); 67 | } 68 | if (host == null || !host.isAlive() || host.isHulk()) { 69 | hostCooldown.advance(amount); 70 | if (hostCooldown.intervalElapsed()) { 71 | ShipAPI newShip = spawnShip(engine); 72 | 73 | playerShips.transferControl(newShip, true, null, (byte) 0); 74 | } 75 | } else { 76 | hostCooldown.setElapsed(0f); 77 | } 78 | 79 | // clients 80 | for (byte connectionID : controlData.keySet()) { 81 | ClientPlayerData player = controlData.get(connectionID); 82 | 83 | ShipAPI ship = player.getShip(); 84 | 85 | IntervalUtil cooldown = cooldowns.get(player.getInstanceID()); 86 | if (cooldown == null) { 87 | cooldown = new IntervalUtil(1f, 1f); 88 | cooldowns.put(player.getInstanceID(), cooldown); 89 | } 90 | 91 | if (ship == null || !ship.isAlive() || ship.isHulk()) { 92 | cooldown.advance(amount); 93 | if (cooldown.intervalElapsed()) { 94 | ShipAPI newShip = spawnShip(engine); 95 | 96 | playerShips.transferControl(newShip, false, player, connectionID); 97 | } 98 | } else { 99 | cooldown.setElapsed(0f); 100 | } 101 | } 102 | 103 | } 104 | 105 | private ShipAPI spawnShip(CombatEngineAPI engine) { 106 | FleetMemberAPI member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, variants.get(0)); 107 | member.getCrewComposition().setCrew(member.getHullSpec().getMaxCrew()); 108 | 109 | CombatFleetManagerAPI fleetManager = engine.getFleetManager(FleetSide.PLAYER); 110 | fleetManager.addToReserves(member); 111 | 112 | Vector2f location = new Vector2f((float) (Math.random() * 2000f - 1000f), (float) (Math.random() * 2000f - 1000f)); 113 | float facing = (float) (Math.random() * 360f); 114 | 115 | ShipAPI ship = fleetManager.spawnFleetMember(member, location, facing, 0f); 116 | ship.setCRAtDeployment(0.7f); 117 | ship.setControlsLocked(false); 118 | 119 | return ship; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/data/missions/MP_FighterTest/MissionDefinition.java: -------------------------------------------------------------------------------- 1 | package data.missions.MP_FighterTest; 2 | 3 | import com.fs.starfarer.api.fleet.FleetGoal; 4 | import com.fs.starfarer.api.fleet.FleetMemberType; 5 | import com.fs.starfarer.api.mission.FleetSide; 6 | import com.fs.starfarer.api.mission.MissionDefinitionAPI; 7 | import com.fs.starfarer.api.mission.MissionDefinitionPlugin; 8 | 9 | public class MissionDefinition implements MissionDefinitionPlugin { 10 | 11 | private static final int NUM_PLAYER = 6; 12 | private static final int NUM_ENEMY = 6; 13 | 14 | @Override 15 | public void defineMission(MissionDefinitionAPI api) { 16 | api.addPlanet(0f, 0f, 150f, "barren-bombarded", 0f, true); 17 | 18 | api.initFleet(FleetSide.PLAYER, "PCS", FleetGoal.ATTACK, false); 19 | api.initFleet(FleetSide.ENEMY, "TTS", FleetGoal.ATTACK, true); 20 | 21 | api.setFleetTagline(FleetSide.PLAYER, "Us"); 22 | api.setFleetTagline(FleetSide.ENEMY, "Them"); 23 | 24 | api.addToFleet(FleetSide.PLAYER, "condor_Attack", FleetMemberType.SHIP, true); 25 | api.addToFleet(FleetSide.ENEMY, "condor_Attack", FleetMemberType.SHIP, false); 26 | 27 | // Set up the map. 28 | float width = 15000f; 29 | float height = 15000f; 30 | api.initMap(-width / 2f, width / 2f, -height / 2f, height / 2f); 31 | 32 | float minX = -width / 2; 33 | float minY = -height / 2; 34 | 35 | for (int i = 0; i < 50; i++) { 36 | float x = (float) Math.random() * width - width / 2; 37 | float y = (float) Math.random() * height - height / 2; 38 | float radius = 100f + (float) Math.random() * 400f; 39 | api.addNebula(x, y, radius); 40 | } 41 | 42 | // Add objectives 43 | api.addObjective(minX + width * 0.25f, minY + height * 0.25f, "nav_buoy"); 44 | api.addObjective(minX + width * 0.35f, minY + height * 0.65f, "comm_relay"); 45 | api.addObjective(minX + width * 0.75f, minY + height * 0.45f, "nav_buoy"); 46 | api.addObjective(minX + width * 0.65f, minY + height * 0.35f, "comm_relay"); 47 | api.addObjective(minX + width * 0.5f, minY + height * 0.25f, "sensor_array"); 48 | } 49 | } -------------------------------------------------------------------------------- /src/data/scripts/console/commands/mpConnectToHost.java: -------------------------------------------------------------------------------- 1 | package data.scripts.console.commands; 2 | 3 | import data.scripts.MPModPlugin; 4 | import data.scripts.plugins.MPClientPlugin; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.lazywizard.console.BaseCommand; 7 | import org.lazywizard.console.Console; 8 | 9 | public class mpConnectToHost implements BaseCommand { 10 | @Override 11 | public CommandResult runCommand(@NotNull String args, @NotNull CommandContext context) { 12 | if (!(context == CommandContext.COMBAT_CAMPAIGN || context == CommandContext.COMBAT_SIMULATION || context == CommandContext.COMBAT_MISSION)) { 13 | Console.showMessage("Command only usable in combat"); 14 | return CommandResult.ERROR; 15 | } 16 | if (args.trim().isEmpty()) { 17 | Console.showMessage("Specify address"); 18 | return CommandResult.BAD_SYNTAX; 19 | } 20 | 21 | String[] ids = args.split(" "); 22 | if (ids.length != 1) { 23 | Console.showMessage("Syntax error"); 24 | return CommandResult.BAD_SYNTAX; 25 | } 26 | 27 | String[] address = ids[0].split(":"); 28 | String host = address[0]; 29 | int port = Integer.parseInt(address[1]); 30 | 31 | Console.showMessage("Starting client on port " + port); 32 | MPModPlugin.setPlugin(new MPClientPlugin(host, port)); 33 | Console.showMessage("Client started successfully"); 34 | 35 | return CommandResult.SUCCESS; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/data/scripts/console/commands/mpConnectToHostCached.java: -------------------------------------------------------------------------------- 1 | package data.scripts.console.commands; 2 | 3 | import com.fs.starfarer.api.Global; 4 | import data.scripts.MPModPlugin; 5 | import data.scripts.plugins.MPClientPlugin; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.lazywizard.console.BaseCommand; 8 | import org.lazywizard.console.Console; 9 | 10 | public class mpConnectToHostCached implements BaseCommand { 11 | @Override 12 | public CommandResult runCommand(@NotNull String args, @NotNull CommandContext context) { 13 | if (!(context == CommandContext.COMBAT_CAMPAIGN || context == CommandContext.COMBAT_SIMULATION || context == CommandContext.COMBAT_MISSION)) { 14 | Console.showMessage("Command only usable in combat"); 15 | return CommandResult.ERROR; 16 | } 17 | 18 | String a = Global.getSettings().getString("mpHost"); 19 | String[] b = a.split(":"); 20 | String host = b[0]; 21 | int port = Integer.parseInt(b[1]); 22 | 23 | Console.showMessage("Starting client on port " + port); 24 | MPModPlugin.setPlugin(new MPClientPlugin(host, port)); 25 | Console.showMessage("Client started successfully"); 26 | 27 | return CommandResult.SUCCESS; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/data/scripts/console/commands/mpFlush.java: -------------------------------------------------------------------------------- 1 | package data.scripts.console.commands; 2 | 3 | import com.fs.starfarer.api.Global; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.lazywizard.console.BaseCommand; 6 | import org.lazywizard.console.Console; 7 | 8 | public class mpFlush implements BaseCommand { 9 | public static final String FLUSH_KEY = "JESSE_WE_NEED_TO_COOK"; 10 | @Override 11 | public CommandResult runCommand(@NotNull String args, @NotNull CommandContext context) { 12 | if (!(context == CommandContext.COMBAT_CAMPAIGN || context == CommandContext.COMBAT_SIMULATION || context == CommandContext.COMBAT_MISSION)) { 13 | Console.showMessage("Command only usable in combat"); 14 | return CommandResult.ERROR; 15 | } 16 | 17 | Console.showMessage("Flushing data"); 18 | Global.getCombatEngine().getCustomData().put(FLUSH_KEY, new Object()); 19 | 20 | return CommandResult.SUCCESS; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/data/scripts/console/commands/mpPilotShip.java: -------------------------------------------------------------------------------- 1 | package data.scripts.console.commands; 2 | 3 | import com.fs.starfarer.api.Global; 4 | import com.fs.starfarer.api.combat.*; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.lazywizard.console.BaseCommand; 7 | import org.lazywizard.console.Console; 8 | import org.lwjgl.util.vector.Vector2f; 9 | 10 | import java.util.Set; 11 | 12 | public class mpPilotShip implements BaseCommand { 13 | 14 | @Override 15 | public CommandResult runCommand(@NotNull String args, @NotNull CommandContext context) { 16 | Console.showMessage("deprecated"); 17 | 18 | for (final ShipAPI ship : Global.getCombatEngine().getShips()) { 19 | ship.setShipAI(new ShipAIPlugin() { 20 | @Override 21 | public void setDoNotFireDelay(float amount) { 22 | 23 | } 24 | 25 | @Override 26 | public void forceCircumstanceEvaluation() { 27 | 28 | } 29 | 30 | @Override 31 | public void advance(float amount) { 32 | ship.giveCommand(ShipCommand.DECELERATE, null, 0); 33 | ship.setHoldFireOneFrame(true); 34 | } 35 | 36 | @Override 37 | public boolean needsRefit() { 38 | return false; 39 | } 40 | 41 | @Override 42 | public ShipwideAIFlags getAIFlags() { 43 | ShipwideAIFlags flags = new ShipwideAIFlags(); 44 | flags.setFlag(ShipwideAIFlags.AIFlags.BACKING_OFF); 45 | return flags; 46 | } 47 | 48 | @Override 49 | public void cancelCurrentManeuver() { 50 | 51 | } 52 | 53 | @Override 54 | public ShipAIConfig getConfig() { 55 | return new ShipAIConfig(); 56 | } 57 | }); 58 | } 59 | 60 | return CommandResult.SUCCESS; 61 | } 62 | 63 | private static ShipAPI getClosest(Vector2f loc, CombatEngineAPI engine, Set occupied) { 64 | ShipAPI out = null; 65 | float d = Float.MAX_VALUE; 66 | for (ShipAPI ship : engine.getShips()) { 67 | if (occupied.contains(ship.getFleetMemberId())) continue; 68 | 69 | float d2 = Vector2f.sub(ship.getLocation(), loc, new Vector2f()).lengthSquared(); 70 | if (d2 < d) { 71 | d = d2; 72 | out = ship; 73 | } 74 | } 75 | return out; 76 | } 77 | } -------------------------------------------------------------------------------- /src/data/scripts/console/commands/mpRunServer.java: -------------------------------------------------------------------------------- 1 | package data.scripts.console.commands; 2 | 3 | import data.scripts.MPModPlugin; 4 | import data.scripts.plugins.MPServerPlugin; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.lazywizard.console.BaseCommand; 7 | import org.lazywizard.console.Console; 8 | 9 | public class mpRunServer implements BaseCommand { 10 | @Override 11 | public CommandResult runCommand(@NotNull String args, @NotNull CommandContext context) { 12 | if (!(context == CommandContext.COMBAT_CAMPAIGN || context == CommandContext.COMBAT_SIMULATION || context == CommandContext.COMBAT_MISSION)) { 13 | Console.showMessage("Command only usable in combat"); 14 | return CommandResult.ERROR; 15 | } 16 | 17 | Console.showMessage("Starting server"); 18 | MPModPlugin.setPlugin(new MPServerPlugin(20303)); 19 | 20 | return CommandResult.SUCCESS; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/data/scripts/misc/MapSet.java: -------------------------------------------------------------------------------- 1 | package data.scripts.misc; 2 | 3 | import java.util.HashMap; 4 | import java.util.HashSet; 5 | import java.util.Map; 6 | import java.util.Set; 7 | 8 | public class MapSet { 9 | 10 | private final Map a = new HashMap<>(); 11 | private final Map b = new HashMap<>(); 12 | 13 | public MapSet() { 14 | 15 | } 16 | 17 | public MapSet(Map map) { 18 | a.putAll(map); 19 | 20 | Set set = new HashSet<>(); 21 | for (T t : map.keySet()) { 22 | U u = map.get(t); 23 | 24 | if (!set.add(u)) { 25 | throw new IllegalArgumentException("mapset constructor received duplicate values"); 26 | } 27 | 28 | b.put(u, t); 29 | } 30 | } 31 | 32 | public U getA(T t) { 33 | return a.get(t); 34 | } 35 | 36 | public T getB(U u) { 37 | return b.get(u); 38 | } 39 | 40 | public void put(T t, U u) { 41 | a.put(t, u); 42 | b.put(u, t); 43 | } 44 | 45 | public Set setA() { 46 | return a.keySet(); 47 | } 48 | 49 | public Set setB() { 50 | return b.keySet(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/DataGenManager.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data; 2 | 3 | import data.scripts.net.data.packables.EntityData; 4 | import data.scripts.net.data.records.DataRecord; 5 | import data.scripts.net.data.tables.InboundEntityManager; 6 | import data.scripts.net.data.tables.OutboundEntityManager; 7 | import data.scripts.plugins.MPPlugin; 8 | 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Set; 13 | 14 | /** 15 | * Allows mods to specify entity types and record types at runtime 16 | */ 17 | public class DataGenManager { 18 | public static Map, Byte> entityTypeIDs = new HashMap<>(); 19 | 20 | public static Map recordTypeIDs = new HashMap<>(); 21 | public static Map> recordInstances = new HashMap<>(); 22 | 23 | public static Map inboundDataDestinations = new HashMap<>(); 24 | public static Map outboundDataSources = new HashMap<>(); 25 | 26 | private static byte idIncrementer = 1; 27 | 28 | public static byte registerEntityType(Class clazz) { 29 | byte id = idIncrementer; 30 | entityTypeIDs.put(clazz, id); 31 | idIncrementer++; 32 | 33 | return id; 34 | } 35 | 36 | public static byte registerRecordType(String c, DataRecord instance) { 37 | byte id = idIncrementer; 38 | recordTypeIDs.put(c, id); 39 | recordInstances.put(id, instance); 40 | idIncrementer++; 41 | return id; 42 | } 43 | 44 | public static void registerInboundEntityManager(byte dataTypeID, InboundEntityManager manager) { 45 | inboundDataDestinations.put(dataTypeID, manager); 46 | } 47 | 48 | public static void registerOutboundEntityManager(byte dataTypeID, OutboundEntityManager manager) { 49 | outboundDataSources.put(dataTypeID, manager); 50 | } 51 | 52 | public static void distributeInboundDeltas(InboundData inbound, MPPlugin plugin, int tick, byte connectionID) { 53 | for (byte type : inbound.in.keySet()) { 54 | Map> entities = inbound.in.get(type); 55 | InboundEntityManager manager = inboundDataDestinations.get(type); 56 | 57 | if (manager == null) { 58 | System.err.println("MANAGER NOT FOUND"); 59 | continue; 60 | } 61 | 62 | for (short instance : entities.keySet()) { 63 | manager.processDelta(type, instance, entities.get(instance), plugin, tick, connectionID); 64 | } 65 | } 66 | 67 | for (byte type : inbound.deleted.keySet()) { 68 | Set instances = inbound.deleted.get(type); 69 | InboundEntityManager manager = inboundDataDestinations.get(type); 70 | 71 | if (manager == null) { 72 | System.err.println("MANAGER NOT FOUND"); 73 | continue; 74 | } 75 | 76 | for (short instance : instances) { 77 | manager.processDeletion(type, instance, plugin, tick, connectionID); 78 | } 79 | } 80 | } 81 | 82 | /** 83 | * An ordered hierarchy of data that will be compressed into a byte buffer 84 | * Order: Type ID -> Instance ID -> Record ID 85 | * @return data hierarchy 86 | */ 87 | public static Map collectOutboundDeltas(float amount, List connectionIDs, OutboundEntityManager.PacketType type) { 88 | Map connectionOutData = new HashMap<>(); 89 | 90 | Map>> out = new HashMap<>(); 91 | Map>> deleted = new HashMap<>(); 92 | 93 | for (byte connectionID : connectionIDs) { 94 | out.put(connectionID, new HashMap>()); 95 | deleted.put(connectionID, new HashMap>()); 96 | } 97 | 98 | for (byte source : outboundDataSources.keySet()) { 99 | OutboundEntityManager manager = outboundDataSources.get(source); 100 | if (manager.getOutboundPacketType() != type) continue; 101 | 102 | Map> connectionEntities = manager.getOutbound(source, amount, connectionIDs); 103 | 104 | for (byte connectionID : connectionEntities.keySet()) { 105 | Map entities = connectionEntities.get(connectionID); 106 | if (entities == null || entities.isEmpty()) continue; 107 | 108 | Map> sourceEntityData = out.get(connectionID); 109 | sourceEntityData.put(source, entities); 110 | } 111 | 112 | for (byte connectionID : connectionIDs) { 113 | Set deletedEntities = manager.getDeleted(source, connectionID); 114 | if (deletedEntities == null || deletedEntities.isEmpty()) continue; 115 | 116 | Map> deletedEntityData = deleted.get(connectionID); 117 | deletedEntityData.put(source, deletedEntities); 118 | } 119 | } 120 | 121 | for (byte connectionID : connectionIDs) { 122 | Map> connectionSourceEntities = out.get(connectionID); 123 | Map> connectionDeletedEntities = deleted.get(connectionID); 124 | 125 | connectionOutData.put(connectionID, new OutboundData(connectionSourceEntities, connectionDeletedEntities, connectionID)); 126 | } 127 | 128 | return connectionOutData; 129 | } 130 | 131 | /** 132 | * workaround to avoid reflection 133 | * @param typeID id 134 | * @return new empty instance 135 | */ 136 | public static DataRecord recordFactory(byte typeID) { 137 | DataRecord out = recordInstances.get(typeID); 138 | if (out == null) { 139 | throw new NullPointerException("No record type found at ID: " + typeID); 140 | } 141 | return out; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/InboundData.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Set; 6 | 7 | public class InboundData { 8 | public final Map>> in; 9 | public final Map> deleted; 10 | public int size = 0; 11 | 12 | public InboundData() { 13 | this(new HashMap>>(), new HashMap>()); 14 | } 15 | 16 | public InboundData(Map>> in, Map> deleted) { 17 | this.in = in; 18 | this.deleted = deleted; 19 | } 20 | 21 | public void setSize(int size) { 22 | this.size = size; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/InstanceData.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data; 2 | 3 | import data.scripts.net.data.records.DataRecord; 4 | 5 | import java.util.Map; 6 | 7 | public class InstanceData { 8 | public int size; 9 | public final Map> records; 10 | 11 | public InstanceData(int size, Map> records) { 12 | this.size = size; 13 | this.records = records; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/OutboundData.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Set; 6 | 7 | public class OutboundData { 8 | private final Map> out; 9 | private final Map> deleted; 10 | private int size = -1; 11 | private final byte connectionID; 12 | 13 | public final Object sync = new Object(); 14 | 15 | public OutboundData(byte connectionID) { 16 | this(new HashMap>(), new HashMap>(), connectionID); 17 | } 18 | 19 | public OutboundData(Map> out, Map> deleted, byte connectionID) { 20 | this.out = out; 21 | this.deleted = deleted; 22 | this.connectionID = connectionID; 23 | } 24 | 25 | public void setSize(int size) { 26 | this.size = size; 27 | } 28 | 29 | public int getSize() { 30 | return size; 31 | } 32 | 33 | public Map> getOut() { 34 | return out; 35 | } 36 | 37 | public Map> getDeleted() { 38 | return deleted; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/datagen/BaseDatagen.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.datagen; 2 | 3 | import data.scripts.plugins.MPPlugin; 4 | 5 | public interface BaseDatagen { 6 | 7 | void generate(MPPlugin plugin); 8 | } 9 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/datagen/FighterVariantDatastore.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.datagen; 2 | 3 | import com.fs.starfarer.api.Global; 4 | import com.fs.starfarer.api.loading.FighterWingSpecAPI; 5 | import data.scripts.plugins.MPPlugin; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class FighterVariantDatastore implements BaseDatagen { 11 | 12 | private final Map variants = new HashMap<>(); 13 | 14 | @Override 15 | public void generate(MPPlugin plugin) { 16 | for (FighterWingSpecAPI spec : Global.getSettings().getAllFighterWingSpecs()) { 17 | String variant = spec.getVariantId(); 18 | String hull = spec.getVariant().getHullSpec().getHullId(); 19 | 20 | if (variant != null && hull != null) { 21 | variants.put(hull, variant); 22 | } else { 23 | throw new NullPointerException("fighter wing hullspec id or variant id was null"); 24 | } 25 | } 26 | } 27 | 28 | public Map getVariants() { 29 | return variants; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/datagen/ProjectileSpecDatastore.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.datagen; 2 | 3 | import com.fs.starfarer.api.Global; 4 | import com.fs.starfarer.api.SettingsAPI; 5 | import com.fs.starfarer.api.loading.MissileSpecAPI; 6 | import com.fs.starfarer.api.loading.ProjectileSpecAPI; 7 | import com.fs.starfarer.api.loading.WeaponSpecAPI; 8 | import data.scripts.plugins.MPPlugin; 9 | import org.json.JSONArray; 10 | import org.json.JSONException; 11 | import org.json.JSONObject; 12 | 13 | import java.io.IOException; 14 | import java.util.ArrayList; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | public class ProjectileSpecDatastore implements BaseDatagen { 20 | 21 | // private final Map weapons = new HashMap<>(); 22 | private final Map missiles = new HashMap<>(); 23 | private final Map projectiles = new HashMap<>(); 24 | private final Map weaponIDs = new HashMap<>(); 25 | private final Map weaponIDKeys = new HashMap<>(); 26 | private final Map projectileIDs = new HashMap<>(); 27 | private final Map projectileIDKeys = new HashMap<>(); 28 | private final Map weaponSpawnIDs = new HashMap<>(); 29 | 30 | /** 31 | * Collate weapon and projectile specs 32 | */ 33 | public void generate(MPPlugin plugin) { 34 | List weaponIDs = new ArrayList<>(); // obf found in StarfarerSettings.getAllWeaponSpecs(); 35 | List projectileIDs = new ArrayList<>(); 36 | for (WeaponSpecAPI spec : Global.getSettings().getAllWeaponSpecs()) { 37 | weaponIDs.add(spec.getWeaponId()); 38 | Object projSpec = spec.getProjectileSpec(); 39 | if (projSpec instanceof ProjectileSpecAPI){ 40 | projectileIDs.add(((ProjectileSpecAPI) projSpec).getId()); 41 | } else if (projSpec instanceof MissileSpecAPI) { 42 | projectileIDs.add(((MissileSpecAPI) projSpec).getHullSpec().getHullId()); 43 | } 44 | } 45 | for (WeaponSpecAPI spec : Global.getSettings().getSystemWeaponSpecs()) { 46 | weaponIDs.add(spec.getWeaponId()); 47 | } 48 | 49 | short index = 0; 50 | for (String id : weaponIDs) { 51 | this.weaponIDs.put(id, index); 52 | this.weaponIDKeys.put(index, id); 53 | index++; 54 | } 55 | 56 | // educated guess based on weaponIDs 57 | 58 | index = 0; 59 | for (String id : projectileIDs) { 60 | this.projectileIDs.put(id, index); 61 | this.projectileIDKeys.put(index, id); 62 | index++; 63 | } 64 | 65 | for (String id : weaponIDs) { 66 | WeaponSpecAPI spec = Global.getSettings().getWeaponSpec(id); 67 | Object o = spec.getProjectileSpec(); 68 | 69 | if (o instanceof MissileSpecAPI) { 70 | MissileSpecAPI m = (MissileSpecAPI) o; 71 | 72 | // try { 73 | // String mirvProjectile = m.getBehaviorJSON().getString("projectileSpec"); 74 | // float f = 0f; 75 | // } catch (Exception e) { 76 | // 77 | // } 78 | 79 | missiles.put(m.getHullSpec().getBaseHullId(), m); 80 | } else if (o instanceof ProjectileSpecAPI) { 81 | ProjectileSpecAPI s = (ProjectileSpecAPI) o; 82 | projectiles.put(s.getId(), s); 83 | } else if (o == null) { // beam 84 | } 85 | } 86 | 87 | SettingsAPI settings = Global.getSettings(); 88 | try { 89 | JSONArray data = settings.getMergedSpreadsheetDataForMod("projectileID","data/config/mp/proj_spawns.csv", "multiplayer"); 90 | 91 | for (int i = 0; i < data.length(); i++) { 92 | JSONObject row = data.getJSONObject(i); 93 | String projectileID = row.getString("projectileID"); 94 | String weaponID = row.getString("weaponID"); 95 | 96 | weaponSpawnIDs.put(projectileID, weaponID); 97 | } 98 | } catch (IOException | JSONException e) { 99 | throw new RuntimeException(e); 100 | } 101 | } 102 | 103 | public Map getWeaponIDs() { 104 | return weaponIDs; 105 | } 106 | 107 | public Map getWeaponIDKeys() { 108 | return weaponIDKeys; 109 | } 110 | 111 | public Map getProjectileIDs() { 112 | return projectileIDs; 113 | } 114 | 115 | public Map getProjectileIDKeys() { 116 | return projectileIDKeys; 117 | } 118 | 119 | public Map getMissiles() { 120 | return missiles; 121 | } 122 | 123 | public Map getProjectiles() { 124 | return projectiles; 125 | } 126 | 127 | public Map getWeaponSpawnIDs() { 128 | return weaponSpawnIDs; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/datagen/ShipVariantDatastore.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.datagen; 2 | 3 | import com.fs.starfarer.api.Global; 4 | import com.fs.starfarer.api.combat.CombatFleetManagerAPI; 5 | import com.fs.starfarer.api.fleet.FleetMemberAPI; 6 | import data.scripts.net.data.packables.entities.ships.VariantData; 7 | import data.scripts.plugins.MPPlugin; 8 | 9 | import java.util.*; 10 | 11 | /** 12 | * Collect and package all data that needs to be transferred before remote combat simulation can begin on client 13 | * e.g. variants 14 | */ 15 | public class ShipVariantDatastore implements BaseDatagen { 16 | 17 | private final Map generated = new HashMap<>(); 18 | private short index = 0; 19 | 20 | /** 21 | * Collects data that needs to be loaded on client side before combat entities can be updated or spawned 22 | */ 23 | public void generate(MPPlugin plugin) { 24 | List members = new ArrayList<>(); 25 | 26 | CombatFleetManagerAPI manager0 = Global.getCombatEngine().getFleetManager(0); 27 | members.addAll(manager0.getDeployedCopy()); 28 | members.addAll(manager0.getReservesCopy()); 29 | 30 | CombatFleetManagerAPI manager1 = Global.getCombatEngine().getFleetManager(1); 31 | members.addAll(manager1.getDeployedCopy()); 32 | members.addAll(manager1.getReservesCopy()); 33 | 34 | for (FleetMemberAPI member : members) { 35 | generated.put(member.getId(), initData(member)); 36 | } 37 | } 38 | 39 | public Map getGenerated() { 40 | return generated; 41 | } 42 | 43 | public void checkVariantUpdate() { 44 | Set keys = new HashSet<>(generated.keySet()); 45 | 46 | List members = new ArrayList<>(); 47 | 48 | CombatFleetManagerAPI manager0 = Global.getCombatEngine().getFleetManager(0); 49 | members.addAll(manager0.getDeployedCopy()); 50 | members.addAll(manager0.getReservesCopy()); 51 | 52 | CombatFleetManagerAPI manager1 = Global.getCombatEngine().getFleetManager(1); 53 | members.addAll(manager1.getDeployedCopy()); 54 | members.addAll(manager1.getReservesCopy()); 55 | 56 | for (FleetMemberAPI member : members) { 57 | if (!keys.remove(member.getId())) { 58 | generated.put(member.getId(), initData(member)); 59 | } 60 | } 61 | } 62 | 63 | private VariantData initData(FleetMemberAPI member) { 64 | index++; 65 | return new VariantData(index, member.getVariant(), member.getId()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/packables/DestExecute.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.packables; 2 | 3 | public interface DestExecute { 4 | void execute(T value, EntityData packable); 5 | } 6 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/packables/EntityData.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.packables; 2 | 3 | import data.scripts.net.data.InstanceData; 4 | import data.scripts.net.data.records.DataRecord; 5 | import data.scripts.net.data.tables.BaseEntityManager; 6 | import data.scripts.net.data.tables.InboundEntityManager; 7 | import data.scripts.plugins.MPPlugin; 8 | 9 | import java.util.ArrayList; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * Used to read/write data 16 | */ 17 | public abstract class EntityData { 18 | 19 | protected short instanceID; 20 | protected final List> records; 21 | protected final List> interpolate; 22 | private boolean flush = true; 23 | 24 | public EntityData() { 25 | this((short) -1); 26 | } 27 | 28 | public EntityData(short instanceID) { 29 | this.instanceID = instanceID; 30 | 31 | records = new ArrayList<>(); 32 | interpolate = new ArrayList<>(); 33 | } 34 | 35 | public short getInstanceID() { 36 | return instanceID; 37 | } 38 | 39 | /** 40 | * Immutable ID type to identify and construct entity when decoding packet 41 | * @return id 42 | */ 43 | public abstract byte getTypeID(); 44 | 45 | protected void addRecord(RecordLambda record) { 46 | records.add(record); 47 | } 48 | 49 | protected void addInterpRecord(InterpRecordLambda recordLambda) { 50 | addRecord(recordLambda); 51 | interpolate.add(recordLambda); 52 | } 53 | 54 | public List> getRecords() { 55 | return records; 56 | } 57 | 58 | /** 59 | * Returns a map of all records that are marked as updated since the last time 60 | * @return deltas 61 | */ 62 | public InstanceData sourceExecute(float amount) { 63 | Map> deltas = new HashMap<>(); 64 | int size = 0; 65 | 66 | for (byte i = 0; i < records.size(); i++) { 67 | RecordLambda recordLambda = records.get(i); 68 | 69 | if (recordLambda.sourceExecute(amount) || flush) { 70 | deltas.put(i, recordLambda.record); 71 | size += recordLambda.record.size(); 72 | } 73 | } 74 | 75 | flush = false; 76 | 77 | return new InstanceData(size, deltas); 78 | } 79 | 80 | /** 81 | * Update stored data with changes from a delta at dest 82 | * @param deltas incoming deltas 83 | */ 84 | public void destExecute(Map deltas, int tick) { 85 | for (byte k : deltas.keySet()) { 86 | RecordLambda record = records.get(k); 87 | record.overwrite(tick, deltas.get(k)); 88 | } 89 | 90 | for (RecordLambda recordLambda : records) recordLambda.destExecute(this); 91 | } 92 | 93 | public void interp(float delay) { 94 | for (InterpRecordLambda interpRecordLambda : interpolate) { 95 | interpRecordLambda.interp(delay, this); 96 | } 97 | } 98 | 99 | /** 100 | * Called every time an entity plugin updates on the game thread. May be called by either client or server 101 | */ 102 | public abstract void update(float amount, BaseEntityManager manager, MPPlugin plugin); 103 | 104 | /** 105 | * Called when entity is initialised 106 | */ 107 | public abstract void init(MPPlugin plugin, InboundEntityManager manager); 108 | 109 | /** 110 | * Called when entity is deleted 111 | */ 112 | public abstract void delete(); 113 | 114 | /** 115 | * Forces the next sourceExecute() call to return all records 116 | */ 117 | public void flush() { 118 | flush = true; 119 | } 120 | 121 | public void setInstanceID(short instanceID) { 122 | this.instanceID = instanceID; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/packables/InterpExecute.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.packables; 2 | 3 | public interface InterpExecute { 4 | 5 | /** 6 | * Interpolate between values v1 and v2 7 | * @param progressive the progressive 0.0 .. 1.0 8 | * @param v1 the older value 9 | * @param v2 the newer value 10 | * @return interpolated value 11 | */ 12 | T interpExecute(float progressive, T v1, T v2); 13 | } 14 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/packables/InterpRecordLambda.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.packables; 2 | 3 | import cmu.CMUtils; 4 | import data.scripts.net.data.records.DataRecord; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | public class InterpRecordLambda extends RecordLambda { 9 | 10 | public static final boolean DEBUG_MODE = false; 11 | 12 | private InterpExecute interpExecute; 13 | 14 | private long timestamp; 15 | private float progressive; // 0.0 to 1.0 16 | private float gap; 17 | private T interpValue; 18 | private T v1; 19 | private T v2; 20 | private int tick; 21 | 22 | public InterpRecordLambda(final DataRecord record, SourceExecute sourceExecute, final DestExecute destExecute) { 23 | super(record, sourceExecute, destExecute); 24 | 25 | this.interpExecute = new DefaultLinterp(); 26 | 27 | v1 = record.getValue(); 28 | v2 = record.getValue(); 29 | interpValue = record.getValue(); 30 | timestamp = System.nanoTime(); 31 | gap = 0f; 32 | progressive = 0f; 33 | tick = -1; 34 | } 35 | 36 | @Override 37 | public void overwrite(int tick, Object delta) { 38 | super.overwrite(tick, delta); 39 | 40 | if (tick > this.tick) { 41 | long n = System.nanoTime(); 42 | long diff = n - timestamp; 43 | timestamp = n; 44 | long milli = TimeUnit.MILLISECONDS.convert(diff, TimeUnit.NANOSECONDS); 45 | gap = milli * 0.001f; 46 | 47 | v2 = v1; 48 | v1 = (T) delta; 49 | 50 | progressive = 0f; 51 | this.tick = tick; 52 | } 53 | } 54 | 55 | @Override 56 | public void destExecute(EntityData packable) { 57 | 58 | } 59 | 60 | public void interp(float amount, EntityData packable) { 61 | progressive += amount; 62 | 63 | float linterp = progressive / gap; 64 | linterp = Math.min(linterp, 1f); 65 | interpValue = interpExecute.interpExecute(linterp, v2, v1); 66 | 67 | destExecute.execute(interpValue, packable); 68 | 69 | if (DEBUG_MODE) { 70 | CMUtils.getGuiDebug().putText( 71 | InterpRecordLambda.class, 72 | "interp_" + this.hashCode(), 73 | record.getDebugText() + " gap:" + String.format("%.3f", gap) + " P: " + String.format("%.4f", progressive) 74 | ); 75 | } 76 | } 77 | 78 | public class DefaultLinterp implements InterpExecute { 79 | @Override 80 | public T interpExecute(float progressive, T v1, T v2) { 81 | return record.linterp(progressive, v1, v2); 82 | } 83 | } 84 | 85 | public InterpRecordLambda setInterpExecute(InterpExecute interpExecute) { 86 | this.interpExecute = interpExecute; 87 | return this; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/packables/RecordLambda.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.packables; 2 | 3 | import com.fs.starfarer.api.util.IntervalUtil; 4 | import data.scripts.net.data.records.DataRecord; 5 | 6 | public class RecordLambda { 7 | 8 | public final DataRecord record; 9 | public final SourceExecute sourceExecute; 10 | public final DestExecute destExecute; 11 | private IntervalUtil interval = null; 12 | 13 | public RecordLambda(DataRecord record, SourceExecute sourceExecute, DestExecute destExecute) { 14 | this.record = record; 15 | this.sourceExecute = sourceExecute; 16 | this.destExecute = destExecute; 17 | } 18 | 19 | public boolean sourceExecute(float amount) { 20 | boolean update = record.sourceExecute(sourceExecute); 21 | 22 | if (interval == null) { 23 | return update; 24 | } else { 25 | interval.advance(amount); 26 | 27 | if (interval.intervalElapsed()) { 28 | return update; 29 | } 30 | } 31 | 32 | return false; 33 | } 34 | 35 | public void overwrite(int tick, Object delta) { 36 | record.overwrite(delta); 37 | } 38 | 39 | public void destExecute(EntityData packable) { 40 | destExecute.execute(record.getValue(), packable); 41 | } 42 | 43 | public RecordLambda setRate(float interval) { 44 | this.interval = new IntervalUtil(interval, interval); 45 | return this; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/packables/SourceExecute.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.packables; 2 | 3 | public interface SourceExecute { 4 | T get(); 5 | } 6 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/packables/entities/ships/ShieldData.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.packables.entities.ships; 2 | 3 | import com.fs.starfarer.api.combat.ShieldAPI; 4 | import com.fs.starfarer.api.combat.ShipAPI; 5 | import data.scripts.net.data.packables.*; 6 | import data.scripts.net.data.records.ByteRecord; 7 | import data.scripts.net.data.records.Float16Record; 8 | import data.scripts.net.data.tables.BaseEntityManager; 9 | import data.scripts.net.data.tables.InboundEntityManager; 10 | import data.scripts.net.data.tables.client.combat.entities.ships.ClientShipTable; 11 | import data.scripts.plugins.MPPlugin; 12 | import org.lazywizard.lazylib.MathUtils; 13 | 14 | public class ShieldData extends EntityData { 15 | 16 | public static byte TYPE_ID; 17 | 18 | private final short instanceID; 19 | private final ShipAPI ship; 20 | 21 | private ShieldAPI shield; 22 | 23 | /** 24 | * Shield must be passed in constructor 25 | */ 26 | public ShieldData(short instanceID, final ShieldAPI shield, final ShipAPI ship) { 27 | super(instanceID); 28 | this.instanceID = instanceID; 29 | this.ship = ship; 30 | 31 | if (shield == null) throw new NullPointerException("Null shield object"); 32 | 33 | addRecord(new RecordLambda<>( 34 | ByteRecord.getDefault().setDebugText("shield active"), 35 | new SourceExecute() { 36 | @Override 37 | public Byte get() { 38 | return shield.isOn() ? (byte) 1 : (byte) 0; 39 | } 40 | }, 41 | new DestExecute() { 42 | @Override 43 | public void execute(Byte value, EntityData packable) { 44 | ShieldData shieldData = (ShieldData) packable; 45 | ShieldAPI shield = shieldData.getShield(); 46 | if (shield != null) { 47 | if (value == (byte) 1) shield.toggleOn(); 48 | else shield.toggleOff(); 49 | } 50 | } 51 | } 52 | )); 53 | addInterpRecord(new InterpRecordLambda<>( 54 | Float16Record.getDefault().setDebugText("shield facing"), 55 | new SourceExecute() { 56 | @Override 57 | public Float get() { 58 | return shield.getFacing(); 59 | } 60 | }, 61 | new DestExecute() { 62 | @Override 63 | public void execute(Float value, EntityData packable) { 64 | ShieldData shieldData = (ShieldData) packable; 65 | ShieldAPI shield = shieldData.getShield(); 66 | if (shield != null && shield.isOn()) { 67 | shield.forceFacing(value); 68 | } 69 | } 70 | } 71 | ).setInterpExecute(new InterpExecute() { 72 | @Override 73 | public Float interpExecute(float progressive, Float v1, Float v2) { 74 | return v1 + (progressive * MathUtils.getShortestRotation(v1, v2)); 75 | } 76 | })); 77 | addInterpRecord(new InterpRecordLambda<>( 78 | Float16Record.getDefault().setDebugText("shield arc"), 79 | new SourceExecute() { 80 | @Override 81 | public Float get() { 82 | return shield.getActiveArc(); 83 | } 84 | }, 85 | new DestExecute() { 86 | @Override 87 | public void execute(Float value, EntityData packable) { 88 | ShieldData shieldData = (ShieldData) packable; 89 | ShieldAPI shield = shieldData.getShield(); 90 | if (shield != null && shield.isOn()) { 91 | shield.setActiveArc(value); 92 | } 93 | } 94 | } 95 | ).setInterpExecute(new InterpExecute() { 96 | @Override 97 | public Float interpExecute(float progressive, Float v1, Float v2) { 98 | return v1 + (progressive * MathUtils.getShortestRotation(v1, v2)); 99 | } 100 | })); 101 | } 102 | 103 | @Override 104 | public byte getTypeID() { 105 | return TYPE_ID; 106 | } 107 | 108 | @Override 109 | public void update(float amount, BaseEntityManager manager, MPPlugin plugin) { 110 | if (plugin.getType() == MPPlugin.PluginType.CLIENT) { 111 | if (shield == null) { 112 | ClientShipTable clientShipTable = (ClientShipTable) manager; 113 | 114 | setShield(clientShipTable.getShipTable().array()[instanceID].getShip().getShield()); 115 | } 116 | } 117 | } 118 | 119 | @Override 120 | public void init(MPPlugin plugin, InboundEntityManager manager) { 121 | 122 | } 123 | 124 | @Override 125 | public void delete() { 126 | 127 | } 128 | 129 | public ShieldAPI getShield() { 130 | return shield; 131 | } 132 | 133 | public void setShield(ShieldAPI shield) { 134 | this.shield = shield; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/packables/metadata/ChatListenData.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.packables.metadata; 2 | 3 | import data.scripts.net.data.packables.DestExecute; 4 | import data.scripts.net.data.packables.EntityData; 5 | import data.scripts.net.data.packables.RecordLambda; 6 | import data.scripts.net.data.packables.SourceExecute; 7 | import data.scripts.net.data.records.ByteRecord; 8 | import data.scripts.net.data.records.collections.ListenArrayRecord; 9 | import data.scripts.net.data.tables.BaseEntityManager; 10 | import data.scripts.net.data.tables.InboundEntityManager; 11 | import data.scripts.plugins.MPPlugin; 12 | import data.scripts.plugins.gui.MPChatboxPlugin; 13 | 14 | import java.nio.charset.StandardCharsets; 15 | import java.util.ArrayList; 16 | import java.util.Iterator; 17 | import java.util.List; 18 | 19 | /** 20 | * Facilitates one-way chat text 21 | */ 22 | public class ChatListenData extends EntityData { 23 | 24 | public static byte TYPE_ID; 25 | 26 | private final List toWrite = new ArrayList<>(); 27 | private final List received = new ArrayList<>(); 28 | 29 | public ChatListenData(short instanceID) { 30 | super(instanceID); 31 | 32 | addRecord(new RecordLambda<>( 33 | new ListenArrayRecord<>(new ArrayList(), ByteRecord.TYPE_ID).setDebugText("chat strings"), 34 | new SourceExecute>() { 35 | @Override 36 | public List get() { 37 | List out = new ArrayList<>(); 38 | 39 | for (MPChatboxPlugin.ChatEntry entry : toWrite) { 40 | out.add(entry.connectionID); 41 | 42 | byte[] s = entry.text.getBytes(StandardCharsets.UTF_8); 43 | byte l = (byte) (s.length & 0xFF); 44 | out.add(l); 45 | 46 | for (int i = 0; i < Math.min(s.length, 255); i++) { 47 | out.add(s[i]); 48 | } 49 | } 50 | 51 | toWrite.clear(); 52 | 53 | return out; 54 | } 55 | }, 56 | new DestExecute>() { 57 | @Override 58 | public void execute(List value, EntityData packable) { 59 | for (Iterator iterator = value.iterator(); iterator.hasNext(); ) { 60 | byte id = iterator.next(); 61 | byte l = iterator.next(); 62 | byte[] s = new byte[l]; 63 | for (int i = 0; i < l; i++) { 64 | s[i] = iterator.next(); 65 | } 66 | 67 | String text = new String(s, StandardCharsets.UTF_8); 68 | 69 | received.add(new MPChatboxPlugin.ChatEntry(text, "unknown", id)); 70 | } 71 | } 72 | } 73 | )); 74 | } 75 | 76 | @Override 77 | public void init(MPPlugin plugin, InboundEntityManager manager) { 78 | 79 | } 80 | 81 | @Override 82 | public void update(float amount, BaseEntityManager manager, MPPlugin plugin) { 83 | 84 | } 85 | 86 | @Override 87 | public void delete() { 88 | 89 | } 90 | 91 | public void submitChatEntry(MPChatboxPlugin.ChatEntry entry) { 92 | toWrite.add(entry); 93 | } 94 | 95 | public List getReceived() { 96 | if (received.isEmpty()) { 97 | return new ArrayList<>(); 98 | } 99 | List out = new ArrayList<>(received); 100 | received.clear(); 101 | return out; 102 | } 103 | 104 | @Override 105 | public byte getTypeID() { 106 | return TYPE_ID; 107 | } 108 | 109 | public static void setTypeId(byte typeId) { 110 | TYPE_ID = typeId; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/packables/metadata/ServerConnectionData.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.packables.metadata; 2 | 3 | import data.scripts.net.data.packables.DestExecute; 4 | import data.scripts.net.data.packables.EntityData; 5 | import data.scripts.net.data.packables.RecordLambda; 6 | import data.scripts.net.data.packables.SourceExecute; 7 | import data.scripts.net.data.records.ByteRecord; 8 | import data.scripts.net.data.tables.BaseEntityManager; 9 | import data.scripts.net.data.tables.InboundEntityManager; 10 | import data.scripts.net.io.BaseConnectionWrapper; 11 | import data.scripts.plugins.MPPlugin; 12 | 13 | public class ServerConnectionData extends EntityData { 14 | 15 | public static byte TYPE_ID; 16 | 17 | private byte connectionState; 18 | private byte connectionID; 19 | private long timestamp; 20 | private long latency; 21 | 22 | public ServerConnectionData(short instanceID, final byte connectionID, final BaseConnectionWrapper connection) { 23 | super(instanceID); 24 | 25 | addRecord(new RecordLambda<>( 26 | ByteRecord.getDefault().setDebugText("connection state"), 27 | new SourceExecute() { 28 | @Override 29 | public Byte get() { 30 | return (byte) connection.getConnectionState().ordinal(); 31 | } 32 | }, 33 | new DestExecute() { 34 | @Override 35 | public void execute(Byte value, EntityData packable) { 36 | ServerConnectionData serverConnectionData = (ServerConnectionData) packable; 37 | serverConnectionData.setConnectionState(value); 38 | } 39 | } 40 | )); 41 | addRecord(new RecordLambda<>( 42 | ByteRecord.getDefault().setDebugText("connection id"), 43 | new SourceExecute() { 44 | @Override 45 | public Byte get() { 46 | return connectionID; 47 | } 48 | }, 49 | new DestExecute() { 50 | @Override 51 | public void execute(Byte value, EntityData packable) { 52 | ServerConnectionData serverConnectionData = (ServerConnectionData) packable; 53 | serverConnectionData.setConnectionID(value); 54 | } 55 | } 56 | )); 57 | addRecord(new RecordLambda<>( 58 | ByteRecord.getDefault().setDebugText("server flag"), 59 | new SourceExecute() { 60 | @Override 61 | public Byte get() { 62 | if (connection.sReceive == 0) { // this is very much a hack but it works 63 | latency = System.currentTimeMillis() - timestamp; 64 | connection.sReceive = 1; 65 | 66 | timestamp = System.currentTimeMillis(); 67 | return (byte) 1; 68 | } 69 | 70 | return (byte) 0; 71 | } 72 | }, 73 | new DestExecute() { 74 | @Override 75 | public void execute(Byte value, EntityData packable) { 76 | connection.sListen = value; 77 | } 78 | } 79 | )); 80 | addRecord(new RecordLambda<>( 81 | ByteRecord.getDefault().setDebugText("client listen"), 82 | new SourceExecute() { 83 | @Override 84 | public java.lang.Byte get() { 85 | return connection.cListen; 86 | } 87 | }, 88 | new DestExecute() { 89 | @Override 90 | public void execute(Byte value, EntityData packable) { 91 | connection.cReceive = value; 92 | } 93 | } 94 | )); 95 | } 96 | 97 | @Override 98 | public void init(MPPlugin plugin, InboundEntityManager manager) { 99 | 100 | } 101 | 102 | @Override 103 | public void update(float amount, BaseEntityManager manager, MPPlugin plugin) { 104 | 105 | } 106 | 107 | @Override 108 | public void delete() { 109 | 110 | } 111 | 112 | @Override 113 | public byte getTypeID() { 114 | return TYPE_ID; 115 | } 116 | 117 | public byte getConnectionState() { 118 | return connectionState; 119 | } 120 | 121 | public void setConnectionState(byte connectionState) { 122 | this.connectionState = connectionState; 123 | } 124 | 125 | public void setConnectionID(byte connectionID) { 126 | this.connectionID = connectionID; 127 | } 128 | 129 | public byte getConnectionID() { 130 | return connectionID; 131 | } 132 | 133 | public long getLatency() { 134 | return latency; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/packables/metadata/ServerPlayerData.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.packables.metadata; 2 | 3 | import data.scripts.net.data.packables.DestExecute; 4 | import data.scripts.net.data.packables.EntityData; 5 | import data.scripts.net.data.packables.RecordLambda; 6 | import data.scripts.net.data.packables.SourceExecute; 7 | import data.scripts.net.data.records.ShortRecord; 8 | import data.scripts.net.data.tables.BaseEntityManager; 9 | import data.scripts.net.data.tables.InboundEntityManager; 10 | import data.scripts.plugins.MPPlugin; 11 | 12 | public class ServerPlayerData extends EntityData { 13 | 14 | public static byte TYPE_ID; 15 | 16 | private short activeID; 17 | private short hostID; 18 | 19 | public ServerPlayerData(short instanceID) { 20 | super(instanceID); 21 | 22 | addRecord(new RecordLambda<>( 23 | ShortRecord.getDefault().setDebugText("active ship id"), 24 | new SourceExecute() { 25 | @Override 26 | public Short get() { 27 | return activeID; 28 | } 29 | }, 30 | new DestExecute() { 31 | @Override 32 | public void execute(Short value, EntityData packable) { 33 | setActiveID(value); 34 | } 35 | } 36 | )); 37 | addRecord(new RecordLambda<>( 38 | ShortRecord.getDefault().setDebugText("host ship id"), 39 | new SourceExecute() { 40 | @Override 41 | public Short get() { 42 | return hostID; 43 | } 44 | }, 45 | new DestExecute() { 46 | @Override 47 | public void execute(Short value, EntityData packable) { 48 | setHostID(value); 49 | } 50 | } 51 | )); 52 | } 53 | 54 | @Override 55 | public byte getTypeID() { 56 | return TYPE_ID; 57 | } 58 | 59 | @Override 60 | public void update(float amount, BaseEntityManager manager, MPPlugin plugin) { 61 | 62 | } 63 | 64 | @Override 65 | public void init(MPPlugin plugin, InboundEntityManager manager) { 66 | 67 | } 68 | 69 | @Override 70 | public void delete() { 71 | 72 | } 73 | 74 | public short getActiveID() { 75 | return activeID; 76 | } 77 | 78 | public void setActiveID(short activeID) { 79 | this.activeID = activeID; 80 | } 81 | 82 | public short getHostID() { 83 | return hostID; 84 | } 85 | 86 | public void setHostID(short hostID) { 87 | this.hostID = hostID; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/plugins/ClientScript.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.plugins; 2 | 3 | /** 4 | * General purpose one-way script for sending/receiving arbitrary data 5 | */ 6 | public abstract class ClientScript { 7 | 8 | static short SCRIPT_ID = -1; 9 | 10 | /** 11 | * Called on SERVER 12 | * @param amount delta time 13 | */ 14 | abstract void advance(float amount); 15 | 16 | /** 17 | * Called on CLIENT 18 | * @param amount delta time 19 | */ 20 | abstract void execute(float amount); 21 | 22 | static 23 | 24 | short getID() { 25 | return SCRIPT_ID; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/plugins/DEMPlugin.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.plugins; 2 | 3 | public class DEMPlugin { 4 | 5 | 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/plugins/ServerScript.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.plugins; 2 | 3 | /** 4 | * General purpose one-way script for sending/receiving arbitrary data 5 | */ 6 | public abstract class ServerScript { 7 | 8 | static short SCRIPT_ID = -1; 9 | 10 | /** 11 | * Called on SERVER 12 | * @param amount delta time 13 | */ 14 | abstract void advance(float amount); 15 | 16 | /** 17 | * Called on CLIENT 18 | * @param amount delta time 19 | */ 20 | abstract void execute(float amount); 21 | 22 | static 23 | 24 | short getID() { 25 | return SCRIPT_ID; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/records/ByteRecord.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.records; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | public class ByteRecord extends DataRecord { 6 | 7 | public static byte TYPE_ID; 8 | 9 | public ByteRecord(byte record) { 10 | super(record); 11 | } 12 | 13 | @Override 14 | public void write(ByteBuf dest) { 15 | dest.writeByte(value); 16 | } 17 | 18 | @Override 19 | public Byte read(ByteBuf in) { 20 | return in.readByte(); 21 | } 22 | 23 | @Override 24 | public Byte linterp(float p, Byte v1, Byte v2) { 25 | int i1 = v1 & 0xFF; 26 | int i2 = v2 & 0xFF; 27 | int d = (i2 - i1); 28 | d += 255; 29 | return (byte) (Math.round(d * p) + i1 - 255); 30 | } 31 | 32 | @Override 33 | public boolean checkUpdate(Byte delta) { 34 | return (byte) value != delta; 35 | } 36 | 37 | public static ByteRecord getDefault() { 38 | return new ByteRecord((byte) 0); 39 | } 40 | 41 | public static void setTypeId(byte typeId) { 42 | ByteRecord.TYPE_ID = typeId; 43 | } 44 | 45 | @Override 46 | public byte getTypeId() { 47 | return TYPE_ID; 48 | } 49 | 50 | @Override 51 | public int size() { 52 | return 1; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/records/ConversionUtils.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.records; 2 | 3 | public class ConversionUtils { 4 | 5 | public static byte floatToByte(float value, float range) { 6 | return (byte) (value * 255f / range); 7 | } 8 | 9 | public static float byteToFloat(byte value, float range) { 10 | return (value & 0xFF) / 255f * range; 11 | } 12 | 13 | // https://jvm-gaming.org/t/16-bit-float-conversion-java-code/55621/2 14 | public static short toHalfFloat(final float v) { 15 | //if(Float.isNaN(v)) throw new UnsupportedOperationException("NaN to half conversion not supported!"); 16 | if(v == Float.POSITIVE_INFINITY) return(short)0x7c00; 17 | if(v == Float.NEGATIVE_INFINITY) return(short)0xfc00; 18 | if(v == 0.0f) return(short)0x0000; 19 | if(v == -0.0f) return(short)0x8000; 20 | if(v > 65504.0f) return 0x7bff; // max value supported by half float 21 | if(v < -65504.0f) return(short)( 0x7bff | 0x8000 ); 22 | if(v > 0.0f && v < 5.96046E-8f) return 0x0001; 23 | if(v < 0.0f && v > -5.96046E-8f) return(short)0x8001; 24 | 25 | final int f = Float.floatToIntBits(v); 26 | 27 | return(short)((( f>>16 ) & 0x8000 ) | (((( f & 0x7f800000 ) - 0x38000000 )>>13 ) & 0x7c00 ) | (( f>>13 ) & 0x03ff )); 28 | } 29 | 30 | public static float toFloat(final short half) { 31 | switch((int)half) 32 | { 33 | case 0x0000 : 34 | return 0.0f; 35 | case 0x7c00 : 36 | return Float.POSITIVE_INFINITY; 37 | default : 38 | return Float.intBitsToFloat((( half & 0x8000 )<<16 ) | ((( half & 0x7c00 ) + 0x1C000 )<<13 ) | (( half & 0x03FF )<<13 )); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/records/DataRecord.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.records; 2 | 3 | import data.scripts.net.data.packables.SourceExecute; 4 | import io.netty.buffer.ByteBuf; 5 | 6 | public abstract class DataRecord { 7 | protected T value; 8 | private String debug = "default record description"; 9 | 10 | public DataRecord(T value) { 11 | this.value = value; 12 | } 13 | 14 | /** 15 | * Executes the source update lambda 16 | * @param sourceExecute the lambda script 17 | * @return true if the data has updated 18 | */ 19 | public boolean sourceExecute(SourceExecute sourceExecute) { 20 | T t = sourceExecute.get(); 21 | boolean isUpdated; 22 | try { 23 | isUpdated = checkUpdate(t); 24 | } catch (NullPointerException n) { 25 | throw new NullPointerException("Null value in record delta check: " + debug); 26 | } 27 | 28 | if (isUpdated) { 29 | value = t; 30 | } 31 | 32 | return isUpdated; 33 | } 34 | 35 | /** 36 | * Get raw data without writing IDs 37 | * @param dest buffer to write to 38 | */ 39 | public abstract void write(ByteBuf dest); 40 | 41 | public abstract T read(ByteBuf in); 42 | 43 | public T linterp(float p, T v1, T v2) { 44 | return value; 45 | } 46 | 47 | public abstract byte getTypeId(); 48 | 49 | public abstract int size(); 50 | 51 | /** 52 | * Return true if updated 53 | * @param delta incoming delta 54 | * @return result 55 | */ 56 | protected abstract boolean checkUpdate(T delta); 57 | 58 | public T getValue() { 59 | return value; 60 | } 61 | 62 | /** 63 | * Overwrite the stored record with new data 64 | * @param delta incoming delta 65 | */ 66 | public void overwrite(Object delta) { 67 | value = (T) delta; 68 | } 69 | 70 | public DataRecord setDebugText(String debug) { 71 | this.debug = debug; 72 | return this; 73 | } 74 | 75 | public String getDebugText() { 76 | return debug; 77 | } 78 | 79 | @Override 80 | public String toString() { 81 | return "value=" + value + 82 | ", debug='" + debug + '\'' + 83 | '}'; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/records/Float16Record.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.records; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | /** 6 | * 16 bit float 7 | */ 8 | public class Float16Record extends DataRecord { 9 | 10 | public static byte TYPE_ID; 11 | 12 | public Float16Record(float record) { 13 | super(record); 14 | } 15 | 16 | @Override 17 | public void write(ByteBuf dest) { 18 | dest.writeShort(ConversionUtils.toHalfFloat(value)); 19 | } 20 | 21 | @Override 22 | public Float read(ByteBuf in) { 23 | short value = in.readShort(); 24 | return ConversionUtils.toFloat(value); 25 | } 26 | 27 | @Override 28 | public Float linterp(float p, Float v1, Float v2) { 29 | return (p * (v2 - v1)) + v1; 30 | } 31 | 32 | @Override 33 | protected boolean checkUpdate(Float delta) { 34 | return !value.equals(delta); 35 | } 36 | 37 | public static DataRecord getDefault() { 38 | return new Float16Record(0f); 39 | } 40 | 41 | public static void setTypeId(byte typeId) { 42 | Float16Record.TYPE_ID = typeId; 43 | } 44 | 45 | @Override 46 | public byte getTypeId() { 47 | return TYPE_ID; 48 | } 49 | 50 | @Override 51 | public int size() { 52 | return 2; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/records/Float32Record.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.records; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | import java.util.Objects; 6 | 7 | public class Float32Record extends DataRecord { 8 | public static byte TYPE_ID; 9 | 10 | public Float32Record(Float record) { 11 | super(record); 12 | } 13 | 14 | @Override 15 | protected boolean checkUpdate(Float delta) { 16 | return !Objects.equals(value, delta); 17 | } 18 | 19 | @Override 20 | public void write(ByteBuf dest) { 21 | dest.writeFloat(value); 22 | } 23 | 24 | @Override 25 | public Float read(ByteBuf in) { 26 | return in.readFloat(); 27 | } 28 | 29 | @Override 30 | public Float linterp(float p, Float v1, Float v2) { 31 | return (p * (v2 - v1)) + v1; 32 | } 33 | 34 | public static void setTypeId(byte typeId) { 35 | Float32Record.TYPE_ID = typeId; 36 | } 37 | 38 | @Override 39 | public byte getTypeId() { 40 | return TYPE_ID; 41 | } 42 | 43 | public static DataRecord getDefault() { 44 | return new Float32Record(0f); 45 | } 46 | 47 | @Override 48 | public int size() { 49 | return 4; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/records/IntRecord.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.records; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | public class IntRecord extends DataRecord { 6 | public static byte TYPE_ID; 7 | 8 | public IntRecord(Integer record) { 9 | super(record); 10 | } 11 | 12 | @Override 13 | protected boolean checkUpdate(Integer delta) { 14 | return value != (int) delta; 15 | } 16 | 17 | @Override 18 | public void write(ByteBuf dest) { 19 | dest.writeInt(value); 20 | } 21 | 22 | @Override 23 | public Integer read(ByteBuf in) { 24 | return in.readInt(); 25 | } 26 | 27 | @Override 28 | public Integer linterp(float p, Integer v1, Integer v2) { 29 | return (int) ((v2 - v1) * p) + v1; 30 | } 31 | 32 | public static void setTypeId(byte typeId) { 33 | IntRecord.TYPE_ID = typeId; 34 | } 35 | 36 | @Override 37 | public byte getTypeId() { 38 | return TYPE_ID; 39 | } 40 | 41 | public static IntRecord getDefault() { 42 | return new IntRecord(0); 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return "IntRecord{" + 48 | "record=" + value + 49 | '}'; 50 | } 51 | 52 | @Override 53 | public int size() { 54 | return 4; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/records/ShortRecord.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.records; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | public class ShortRecord extends DataRecord { 6 | public static byte TYPE_ID; 7 | 8 | public ShortRecord(Short record) { 9 | super(record); 10 | } 11 | 12 | @Override 13 | public boolean checkUpdate(Short delta) { 14 | return value != (short) delta; 15 | } 16 | 17 | @Override 18 | public void write(ByteBuf dest) { 19 | dest.writeShort(value); 20 | } 21 | 22 | @Override 23 | public Short read(ByteBuf in) { 24 | return in.readShort(); 25 | } 26 | 27 | @Override 28 | public Short linterp(float p, Short v1, Short v2) { 29 | return (short) ((p * (v2 - v1)) + v1); 30 | } 31 | 32 | public static void setTypeId(byte typeId) { 33 | ShortRecord.TYPE_ID = typeId; 34 | } 35 | 36 | @Override 37 | public byte getTypeId() { 38 | return TYPE_ID; 39 | } 40 | 41 | public static ShortRecord getDefault() { 42 | return new ShortRecord((short) 0); 43 | } 44 | 45 | @Override 46 | public int size() { 47 | return 2; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/records/StringRecord.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.records; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | import java.nio.charset.Charset; 6 | import java.nio.charset.StandardCharsets; 7 | 8 | public class StringRecord extends DataRecord { 9 | public static byte TYPE_ID; 10 | 11 | public static final String NULL = "NULL"; 12 | 13 | private static final Charset CHARSET = StandardCharsets.UTF_8; 14 | 15 | public StringRecord(String value) { 16 | super(value); 17 | } 18 | 19 | @Override 20 | public void write(ByteBuf dest) { 21 | byte[] bytes = value == null ? NULL.getBytes(CHARSET) : value.getBytes(CHARSET); 22 | 23 | if (bytes.length > Byte.MAX_VALUE) { 24 | byte[] b = new byte[Byte.MAX_VALUE]; 25 | System.arraycopy(bytes, 0, b, 0, b.length); 26 | bytes = b; 27 | } 28 | 29 | dest.writeByte(bytes.length); 30 | dest.writeBytes(bytes); 31 | } 32 | 33 | @Override 34 | public String read(ByteBuf in) { 35 | int length = in.readByte(); 36 | String value = in.readCharSequence(length, CHARSET).toString(); 37 | if (value.equals("NONE")) value = null; 38 | 39 | return value; 40 | } 41 | 42 | @Override 43 | public boolean checkUpdate(String delta) { 44 | return value != null && !value.equals(delta); 45 | } 46 | 47 | public static void setTypeId(byte typeId) { 48 | StringRecord.TYPE_ID = typeId; 49 | } 50 | 51 | @Override 52 | public byte getTypeId() { 53 | return TYPE_ID; 54 | } 55 | 56 | public static StringRecord getDefault() { 57 | return new StringRecord("DEFAULT"); 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return "StringRecord{" + 63 | "record='" + value + '\'' + 64 | '}'; 65 | } 66 | 67 | @Override 68 | public synchronized int size() { 69 | if (value == null) return 1 + NULL.getBytes(CHARSET).length; 70 | return 1 + value.getBytes(CHARSET).length; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/records/Vector2f16Record.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.records; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.lwjgl.util.vector.Vector2f; 5 | 6 | public class Vector2f16Record extends DataRecord { 7 | public static byte TYPE_ID; 8 | 9 | public Vector2f16Record(Vector2f record) { 10 | super(record); 11 | } 12 | 13 | @Override 14 | public boolean checkUpdate(Vector2f delta) { 15 | boolean isUpdated; 16 | 17 | isUpdated = (value.x != delta.x) || (value.y != delta.y); 18 | 19 | return isUpdated; 20 | } 21 | 22 | @Override 23 | public void write(ByteBuf dest) { 24 | dest.writeShort(ConversionUtils.toHalfFloat(value.x)); 25 | dest.writeShort(ConversionUtils.toHalfFloat(value.y)); 26 | } 27 | 28 | @Override 29 | public Vector2f read(ByteBuf in) { 30 | float x = ConversionUtils.toFloat(in.readShort()); 31 | float y = ConversionUtils.toFloat(in.readShort()); 32 | return new Vector2f(x, y); 33 | } 34 | 35 | @Override 36 | public Vector2f linterp(float p, Vector2f v1, Vector2f v2) { 37 | float x = (p * (v2.x - v1.x)) + v1.x; 38 | float y = (p * (v2.y - v1.y)) + v1.y; 39 | return new Vector2f(x, y); 40 | } 41 | 42 | public static void setTypeId(byte typeId) { 43 | Vector2f16Record.TYPE_ID = typeId; 44 | } 45 | 46 | @Override 47 | public byte getTypeId() { 48 | return TYPE_ID; 49 | } 50 | 51 | public static Vector2f16Record getDefault() { 52 | return new Vector2f16Record(new Vector2f(0f, 0f)); 53 | } 54 | 55 | @Override 56 | public int size() { 57 | return 4; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/records/Vector2f32Record.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.records; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.lwjgl.util.vector.Vector2f; 5 | 6 | public class Vector2f32Record extends DataRecord { 7 | public static byte TYPE_ID; 8 | 9 | public Vector2f32Record(Vector2f record) { 10 | super(record); 11 | } 12 | 13 | @Override 14 | public boolean checkUpdate(Vector2f delta) { 15 | boolean isUpdated; 16 | 17 | isUpdated = (value.x != delta.x) || (value.y != delta.y); 18 | 19 | return isUpdated; 20 | } 21 | 22 | @Override 23 | public void write(ByteBuf dest) { 24 | dest.writeFloat(value.x); 25 | dest.writeFloat(value.y); 26 | } 27 | 28 | @Override 29 | public Vector2f read(ByteBuf in) { 30 | float x = in.readFloat(); 31 | float y = in.readFloat(); 32 | return new Vector2f(x, y); 33 | } 34 | 35 | @Override 36 | public Vector2f linterp(float p, Vector2f v1, Vector2f v2) { 37 | float x = (p * (v2.x - v1.x)) + v1.x; 38 | float y = (p * (v2.y - v1.y)) + v1.y; 39 | return new Vector2f(x, y); 40 | } 41 | 42 | public static void setTypeId(byte typeId) { 43 | Vector2f32Record.TYPE_ID = typeId; 44 | } 45 | 46 | @Override 47 | public byte getTypeId() { 48 | return TYPE_ID; 49 | } 50 | 51 | public static Vector2f32Record getDefault() { 52 | return new Vector2f32Record(new Vector2f(0f, 0f)); 53 | } 54 | 55 | @Override 56 | public String toString() { 57 | return "Vector2fRecord{" + 58 | "record=" + value + 59 | '}'; 60 | } 61 | 62 | @Override 63 | public int size() { 64 | return 8; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/records/Vector3f32Record.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.records; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import org.lwjgl.util.vector.Vector3f; 5 | 6 | public class Vector3f32Record extends DataRecord { 7 | 8 | public static byte TYPE_ID; 9 | 10 | public Vector3f32Record(Vector3f record) { 11 | super(record); 12 | } 13 | 14 | @Override 15 | public void write(ByteBuf dest) { 16 | dest.writeFloat(value.x); 17 | dest.writeFloat(value.y); 18 | dest.writeFloat(value.z); 19 | } 20 | 21 | @Override 22 | public Vector3f read(ByteBuf in) { 23 | float x = in.readFloat(); 24 | float y = in.readFloat(); 25 | float z = in.readFloat(); 26 | return new Vector3f(x, y, z); 27 | } 28 | 29 | @Override 30 | public Vector3f linterp(float p, Vector3f v1, Vector3f v2) { 31 | float x = (p * (v2.x - v1.x)) + v1.x; 32 | float y = (p * (v2.y - v1.y)) + v1.y; 33 | float z = (p * (v2.z - v1.z)) + v1.z; 34 | return new Vector3f(x, y, z); 35 | } 36 | 37 | @Override 38 | public boolean checkUpdate(Vector3f delta) { 39 | boolean isUpdated; 40 | 41 | isUpdated = (value.x != delta.x) || (value.y != delta.y) || (value.z != delta.z); 42 | 43 | return isUpdated; 44 | } 45 | 46 | public static void setTypeId(byte typeId) { 47 | Vector3f32Record.TYPE_ID = typeId; 48 | } 49 | 50 | @Override 51 | public byte getTypeId() { 52 | return TYPE_ID; 53 | } 54 | 55 | public static Vector3f32Record getDefault() { 56 | return new Vector3f32Record(new Vector3f(0f, 0f, 0f)); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return "Vector3fRecord{" + 62 | "value=" + value + 63 | '}'; 64 | } 65 | 66 | @Override 67 | public int size() { 68 | return 12; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/records/collections/ListenArrayRecord.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.records.collections; 2 | 3 | import data.scripts.net.data.records.DataRecord; 4 | import data.scripts.net.data.DataGenManager; 5 | import io.netty.buffer.ByteBuf; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | /** 11 | * Sends a one-way array of values. Useful for lists of data that don't need feedback. 12 | * @param Value type 13 | */ 14 | public class ListenArrayRecord extends DataRecord> { 15 | 16 | public static byte TYPE_ID; 17 | private final byte elementTypeID; 18 | 19 | private final DataRecord writer; 20 | 21 | public ListenArrayRecord(List value, byte elementTypeID) { 22 | super(value); 23 | 24 | this.elementTypeID = elementTypeID; 25 | 26 | if (elementTypeID != (byte) -1) { 27 | writer = (DataRecord) DataGenManager.recordFactory(elementTypeID); 28 | } else { 29 | writer = null; 30 | } 31 | } 32 | 33 | @Override 34 | public void overwrite(Object delta) { 35 | super.overwrite(delta); 36 | } 37 | 38 | @Override 39 | public void write(ByteBuf dest) { 40 | if (value.size() > 0b11111111) { 41 | throw new RuntimeException("List size exceeded " + 0b11111111 + " elements"); 42 | } 43 | 44 | dest.writeByte(elementTypeID); 45 | dest.writeByte(value.size()); 46 | 47 | for (E e : value) { 48 | writer.overwrite(e); 49 | writer.write(dest); 50 | } 51 | } 52 | 53 | @Override 54 | public List read(ByteBuf in) { 55 | List out = new ArrayList<>(); 56 | 57 | byte type = in.readByte(); 58 | int num = in.readByte() & 0xFF; 59 | 60 | DataRecord reader = (DataRecord) DataGenManager.recordFactory(type); 61 | 62 | for (int i = 0; i < num; i++) { 63 | out.add(reader.read(in)); 64 | } 65 | 66 | return out; 67 | } 68 | 69 | @Override 70 | public byte getTypeId() { 71 | return TYPE_ID; 72 | } 73 | 74 | @Override 75 | protected boolean checkUpdate(List delta) { 76 | return !delta.isEmpty(); 77 | } 78 | 79 | public static void setTypeId(byte typeId) { 80 | ListenArrayRecord.TYPE_ID = typeId; 81 | } 82 | 83 | @Override 84 | public int size() { 85 | return 2 + (value.size() * writer.size()); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/records/collections/SyncingListRecord.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.records.collections; 2 | 3 | import data.scripts.net.data.packables.SourceExecute; 4 | import data.scripts.net.data.records.DataRecord; 5 | import data.scripts.net.data.DataGenManager; 6 | import io.netty.buffer.ByteBuf; 7 | 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * Attempts to sync a List of values 15 | * broken pls fix 16 | * @param Value type 17 | */ 18 | public class SyncingListRecord extends DataRecord> { 19 | public static byte TYPE_ID; 20 | private final byte elementTypeID; 21 | 22 | private final DataRecord writer; 23 | 24 | private final Map toWrite = new HashMap<>(); 25 | 26 | public SyncingListRecord(List collection, byte elementTypeID) { 27 | super(collection); 28 | this.elementTypeID = elementTypeID; 29 | 30 | if (elementTypeID != (byte) -1) { 31 | writer = (DataRecord) DataGenManager.recordFactory(elementTypeID); 32 | } else { 33 | writer = null; 34 | } 35 | } 36 | 37 | @Override 38 | public boolean sourceExecute(SourceExecute> sourceExecute) { 39 | // List delta = sourceExecute.get(); 40 | // 41 | // boolean update = false; 42 | // 43 | // for (byte i = 0; i < delta.size(); i++) { 44 | // E d = delta.get(i); 45 | // 46 | // if (i + 1 > value.size()) { 47 | // toWrite.put(i, d); 48 | // update = true; 49 | // } else if (!d.equals(value.get(i))) { 50 | // toWrite.put(i, d); 51 | // update = true; 52 | // } 53 | // } 54 | // 55 | // value = delta; 56 | // 57 | // return update; 58 | value = sourceExecute.get(); 59 | return true; 60 | } 61 | 62 | 63 | @Override 64 | public void write(ByteBuf dest) { 65 | if (value.size() > Byte.MAX_VALUE) { 66 | throw new RuntimeException("List size exceeded " + Byte.MAX_VALUE + " elements"); 67 | } 68 | 69 | // if (toWrite.size() == 0) return; 70 | // 71 | // dest.writeByte(elementTypeID); 72 | // dest.writeByte(toWrite.size()); 73 | // 74 | // for (byte i : toWrite.keySet()) { 75 | // // write index 76 | // dest.writeByte(i); 77 | // 78 | // // write data 79 | // writer.overwrite(toWrite.get(i)); 80 | // writer.write(dest); 81 | // } 82 | // 83 | // toWrite.clear(); 84 | 85 | dest.writeByte(elementTypeID); 86 | dest.writeByte(value.size()); 87 | 88 | for (byte i = 0; i < value.size(); i++) { 89 | E e = value.get(i); 90 | 91 | dest.writeByte(i); 92 | 93 | writer.overwrite(e); 94 | writer.write(dest); 95 | } 96 | } 97 | 98 | @Override 99 | public List read(ByteBuf in) { 100 | byte type = in.readByte(); 101 | byte num = in.readByte(); 102 | 103 | DataRecord reader = (DataRecord) DataGenManager.recordFactory(type); 104 | 105 | Map data = new HashMap<>(); 106 | int max = 0; 107 | for (int i = 0; i < num; i++) { 108 | byte index = in.readByte(); 109 | 110 | max = Math.max(index, max); 111 | 112 | E e = reader.read(in); 113 | 114 | data.put(index, e); 115 | } 116 | max++; 117 | 118 | List value = new ArrayList<>(max); 119 | for (int i = 0; i < max; i++) { 120 | value.add(null); 121 | } 122 | 123 | for (byte b : data.keySet()) { 124 | value.set(b, data.get(b)); 125 | } 126 | 127 | return value; 128 | } 129 | 130 | @Override 131 | public void overwrite(Object o) { 132 | List delta = (List) o; 133 | 134 | List temp; 135 | int size = Math.max(delta.size(), value.size()); 136 | temp = new ArrayList<>(size); 137 | for (int i = 0; i < size; i++) { 138 | temp.add(null); 139 | } 140 | 141 | for (int i = 0; i < size; i++) { 142 | E d = i < delta.size() ? delta.get(i) : null; 143 | E v = i < value.size() ? value.get(i) : null; 144 | 145 | if (d != null) { 146 | temp.set(i, d); 147 | } else if (v != null) { 148 | temp.set(i, v); 149 | } 150 | } 151 | 152 | value = temp; 153 | } 154 | 155 | public static void setTypeId(byte typeId) { 156 | SyncingListRecord.TYPE_ID = typeId; 157 | } 158 | 159 | @Override 160 | public byte getTypeId() { 161 | return TYPE_ID; 162 | } 163 | 164 | @Override 165 | protected boolean checkUpdate(List delta) { 166 | return true; 167 | } 168 | 169 | @Override 170 | public int size() { 171 | return 2 + (value.size() * writer.size()); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/records/collections/SyncingMapRecord.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.records.collections; 2 | 3 | import data.scripts.net.data.records.DataRecord; 4 | import io.netty.buffer.ByteBuf; 5 | 6 | import java.util.Map; 7 | 8 | public class SyncingMapRecord extends DataRecord> { 9 | 10 | public SyncingMapRecord(Map value) { 11 | super(value); 12 | } 13 | 14 | @Override 15 | public void write(ByteBuf dest) { 16 | 17 | } 18 | 19 | @Override 20 | public Map read(ByteBuf in) { 21 | return null; 22 | } 23 | 24 | @Override 25 | public byte getTypeId() { 26 | return 0; 27 | } 28 | 29 | @Override 30 | public int size() { 31 | return 0; 32 | } 33 | 34 | @Override 35 | protected boolean checkUpdate(Map delta) { 36 | return false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/BaseEntityManager.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables; 2 | 3 | import data.scripts.plugins.MPPlugin; 4 | 5 | public interface BaseEntityManager { 6 | 7 | void update(float amount, MPPlugin plugin); 8 | 9 | void register(); 10 | } 11 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/EntityInstanceMap.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables; 2 | 3 | import data.scripts.net.data.packables.EntityData; 4 | 5 | import java.util.HashMap; 6 | import java.util.HashSet; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | public class EntityInstanceMap { 11 | 12 | public final Map registered; 13 | public final Set deleted; 14 | 15 | public EntityInstanceMap() { 16 | registered = new HashMap<>(); 17 | deleted = new HashSet<>(); 18 | } 19 | 20 | public EntityInstanceMap(Map registered, Set deleted) { 21 | this.registered = registered; 22 | this.deleted = deleted; 23 | } 24 | 25 | public void delete(short index) { 26 | registered.remove(index); 27 | deleted.add(index); 28 | } 29 | 30 | public Set getDeleted() { 31 | Set out = new HashSet<>(deleted); 32 | deleted.clear(); 33 | return out; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/EntityTable.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables; 2 | 3 | import data.scripts.net.data.packables.EntityData; 4 | 5 | import java.util.LinkedList; 6 | import java.util.Queue; 7 | 8 | public class EntityTable { 9 | private final T[] table; 10 | public short limit = 0; // highest number of elements 11 | 12 | private final Queue vacant; 13 | 14 | public EntityTable(T[] array) { 15 | table = array; 16 | vacant = new LinkedList<>(); 17 | } 18 | 19 | public short add(T t) { 20 | short id; 21 | 22 | if (vacant.isEmpty()) { 23 | id = limit; 24 | limit++; 25 | } else { 26 | id = vacant.poll(); 27 | } 28 | 29 | if (id < 0 || id > table.length - 1) { 30 | throw new NullPointerException("No vacant entity index found"); 31 | } 32 | 33 | table[id] = t; 34 | 35 | return id; 36 | } 37 | 38 | public void remove(short id) { 39 | vacant.add(id); 40 | table[id] = null; 41 | } 42 | 43 | public void set(short id, T t) { 44 | limit = (short) Math.max(id + 1, limit); 45 | table[id] = t; 46 | } 47 | 48 | public long hash() { 49 | return 0; 50 | } 51 | 52 | public T[] array() { 53 | return table; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/InboundEntityManager.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables; 2 | 3 | import data.scripts.plugins.MPPlugin; 4 | 5 | import java.util.Map; 6 | 7 | public interface InboundEntityManager extends BaseEntityManager { 8 | 9 | /** 10 | * Process an inbound delta 11 | * @param typeID the entity type ID 12 | * @param instanceID the unique instance ID of the entity 13 | * @param toProcess a map of records IDs to data values 14 | * @param plugin the plugin running in the local game 15 | * @param tick the current tick at which time the packet was sent 16 | * @param connectionID the source connection ID of the delta 17 | */ 18 | void processDelta(byte typeID, short instanceID, Map toProcess, MPPlugin plugin, int tick, byte connectionID); 19 | 20 | /** 21 | * Indicates that the remote connection is signaling for an entity instance to be deleted 22 | * @param typeID the entity type ID 23 | * @param instanceID the unique instance ID of the entity 24 | * @param plugin the plugin running in the local game 25 | * @param tick the current tick at which time the packet was sent 26 | */ 27 | void processDeletion(byte typeID, short instanceID, MPPlugin plugin, int tick, byte connectionID); 28 | } 29 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/OutboundEntityManager.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables; 2 | 3 | import data.scripts.net.data.InstanceData; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Set; 8 | 9 | public interface OutboundEntityManager extends BaseEntityManager { 10 | 11 | enum PacketType { 12 | SOCKET, 13 | DATAGRAM 14 | } 15 | 16 | Map> getOutbound(byte typeID, float amount, List connectionIDs); 17 | 18 | Set getDeleted(byte typeID, byte connectionID); 19 | 20 | PacketType getOutboundPacketType(); 21 | } 22 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/client/ClientCustomDataTable.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables.client; 2 | 3 | public class ClientCustomDataTable { 4 | } 5 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/client/combat/connection/LobbyInput.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables.client.combat.connection; 2 | 3 | import data.scripts.net.data.DataGenManager; 4 | import data.scripts.net.data.packables.metadata.LobbyData; 5 | import data.scripts.net.data.tables.InboundEntityManager; 6 | import data.scripts.plugins.MPPlugin; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class LobbyInput implements InboundEntityManager { 12 | 13 | private final byte clientID; 14 | private LobbyData lobby; 15 | 16 | private final Map usernames = new HashMap<>(); 17 | private final Map pilotedShipIDs = new HashMap<>(); 18 | 19 | public LobbyInput(byte clientID) { 20 | this.clientID = clientID; 21 | lobby = null; 22 | } 23 | 24 | @Override 25 | public void update(float amount, MPPlugin plugin) { 26 | if (lobby != null) { 27 | lobby.update(amount, this, plugin); 28 | 29 | usernames.putAll(lobby.getPlayerUsernames()); 30 | pilotedShipIDs.putAll(lobby.getPlayerShipIDs()); 31 | 32 | // Short pilotedShipID = pilotedShipIDs.get(clientID); 33 | // if (!Objects.equals(pilotedShipID, localPilotedShipID)) { 34 | // ShipAPI ship = ((MPClientPlugin) plugin).getShipTable().getShips().get(pilotedShipID).getShip(); 35 | // if (ship != null) { 36 | // Global.getCombatEngine().setPlayerShipExternal(ship); 37 | // } 38 | // } 39 | 40 | // localPilotedShipID = pilotedShipID; 41 | } 42 | } 43 | 44 | @Override 45 | public void register() { 46 | DataGenManager.registerInboundEntityManager(LobbyData.TYPE_ID, this); 47 | } 48 | 49 | @Override 50 | public void processDelta(byte typeID, short instanceID, Map toProcess, MPPlugin plugin, int tick, byte connectionID) { 51 | if (lobby == null) { 52 | lobby = new LobbyData(instanceID, null, null); 53 | 54 | lobby.destExecute(toProcess, tick); 55 | 56 | lobby.init(plugin, this); 57 | } else { 58 | lobby.destExecute(toProcess, tick); 59 | } 60 | } 61 | 62 | @Override 63 | public void processDeletion(byte typeID, short instanceID, MPPlugin plugin, int tick, byte connectionID) { 64 | LobbyData data = lobby; 65 | 66 | if (data != null) { 67 | data.delete(); 68 | lobby = null; 69 | } 70 | } 71 | 72 | public LobbyData getLobby() { 73 | return lobby; 74 | } 75 | 76 | public Map getUsernames() { 77 | return usernames; 78 | } 79 | 80 | public Map getPilotedShipIDs() { 81 | return pilotedShipIDs; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/client/combat/connection/TextChatClient.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables.client.combat.connection; 2 | 3 | import com.fs.starfarer.api.Global; 4 | import data.scripts.net.data.DataGenManager; 5 | import data.scripts.net.data.InstanceData; 6 | import data.scripts.net.data.packables.metadata.ChatListenData; 7 | import data.scripts.net.data.tables.InboundEntityManager; 8 | import data.scripts.net.data.tables.OutboundEntityManager; 9 | import data.scripts.plugins.MPPlugin; 10 | import data.scripts.plugins.gui.MPChatboxPlugin; 11 | 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Set; 16 | 17 | public class TextChatClient implements InboundEntityManager, OutboundEntityManager { 18 | 19 | private final ChatListenData send; 20 | private final ChatListenData receive; 21 | private final short instanceID; 22 | private final byte connectionID; 23 | private final MPChatboxPlugin chatbox; 24 | private final LobbyInput lobby; 25 | private final String username; 26 | 27 | public TextChatClient(short instanceID, byte connectionID, MPChatboxPlugin chatbox, LobbyInput lobby) { 28 | send = new ChatListenData(instanceID); 29 | receive = new ChatListenData(instanceID); 30 | this.instanceID = instanceID; 31 | this.connectionID = connectionID; 32 | this.chatbox = chatbox; 33 | this.lobby = lobby; 34 | 35 | username = Global.getSettings().getString("MP_UsernameString"); 36 | } 37 | 38 | @Override 39 | public void update(float amount, MPPlugin plugin) { 40 | send.update(amount, this, plugin); 41 | receive.update(amount, this, plugin); 42 | 43 | String input = chatbox.getInput(); 44 | if (input != null) { 45 | send.submitChatEntry(new MPChatboxPlugin.ChatEntry(input, username, connectionID)); 46 | } 47 | 48 | List fresh = receive.getReceived(); 49 | for (MPChatboxPlugin.ChatEntry entry : fresh) { 50 | entry.username = lobby.getUsernames().get(entry.connectionID); 51 | chatbox.addEntry(entry); 52 | } 53 | } 54 | 55 | @Override 56 | public void register() { 57 | DataGenManager.registerInboundEntityManager(ChatListenData.TYPE_ID, this); 58 | DataGenManager.registerOutboundEntityManager(ChatListenData.TYPE_ID, this); 59 | } 60 | 61 | @Override 62 | public void processDelta(byte typeID, short instanceID, Map toProcess, MPPlugin plugin, int tick, byte connectionID) { 63 | receive.destExecute(toProcess, tick); 64 | } 65 | 66 | @Override 67 | public void processDeletion(byte typeID, short instanceID, MPPlugin plugin, int tick, byte connectionID) { 68 | 69 | } 70 | 71 | @Override 72 | public Map> getOutbound(byte typeID, float amount, List connectionIDs) { 73 | Map> out = new HashMap<>(); 74 | 75 | Map connectionOutData = new HashMap<>(); 76 | 77 | InstanceData instanceData = send.sourceExecute(amount); 78 | 79 | if (instanceData != null && instanceData.size > 0) { 80 | connectionOutData.put(instanceID, instanceData); 81 | } 82 | 83 | for (byte connectionID : connectionIDs) { 84 | out.put(connectionID, connectionOutData); 85 | } 86 | 87 | return out; 88 | } 89 | 90 | @Override 91 | public Set getDeleted(byte typeID, byte connectionID) { 92 | return null; 93 | } 94 | 95 | @Override 96 | public PacketType getOutboundPacketType() { 97 | return PacketType.SOCKET; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/client/combat/entities/ClientProjectileTable.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables.client.combat.entities; 2 | 3 | import com.fs.starfarer.api.Global; 4 | import com.fs.starfarer.api.combat.DamagingProjectileAPI; 5 | import data.scripts.net.data.DataGenManager; 6 | import data.scripts.net.data.packables.entities.projectiles.BallisticProjectileData; 7 | import data.scripts.net.data.packables.entities.projectiles.MissileData; 8 | import data.scripts.net.data.packables.entities.projectiles.MovingRayData; 9 | import data.scripts.net.data.tables.InboundEntityManager; 10 | import data.scripts.plugins.MPPlugin; 11 | 12 | import java.util.HashMap; 13 | import java.util.HashSet; 14 | import java.util.Map; 15 | import java.util.Set; 16 | 17 | public class ClientProjectileTable implements InboundEntityManager { 18 | 19 | private final Map movingRays = new HashMap<>(); 20 | private final Map ballisticProjectiles = new HashMap<>(); 21 | private final Map missiles = new HashMap<>(); 22 | 23 | public ClientProjectileTable() { 24 | } 25 | 26 | @Override 27 | public void update(float amount, MPPlugin plugin) { 28 | Set p = new HashSet<>(Global.getCombatEngine().getProjectiles()); 29 | 30 | for (MovingRayData data : movingRays.values()) { 31 | if (data != null) { 32 | data.update(amount, this, plugin); 33 | data.interp(amount); 34 | 35 | p.remove(data.getProjectile()); 36 | } 37 | } 38 | for (BallisticProjectileData data : ballisticProjectiles.values()) { 39 | if (data != null) { 40 | data.update(amount, this, plugin); 41 | data.interp(amount); 42 | 43 | p.remove(data.getProjectile()); 44 | } 45 | } 46 | for (MissileData data : missiles.values()) { 47 | if (data != null) { 48 | data.update(amount, this, plugin); 49 | data.interp(amount); 50 | 51 | p.remove(data.getProjectile()); 52 | } 53 | } 54 | 55 | for (DamagingProjectileAPI proj : p) { 56 | Global.getCombatEngine().removeEntity(proj); 57 | } 58 | } 59 | 60 | @Override 61 | public void processDelta(byte typeID, short instanceID, Map toProcess, MPPlugin plugin, int tick, byte connectionID) { 62 | if (typeID == MovingRayData.TYPE_ID) { 63 | MovingRayData data = movingRays.get(instanceID); 64 | 65 | if (data == null) { 66 | data = new MovingRayData(instanceID, null, (short) -1, null); 67 | movingRays.put(instanceID, data); 68 | 69 | data.destExecute(toProcess, tick); 70 | 71 | data.init(plugin, this); 72 | } else { 73 | data.destExecute(toProcess, tick); 74 | } 75 | } else if (typeID == BallisticProjectileData.TYPE_ID) { 76 | BallisticProjectileData data = ballisticProjectiles.get(instanceID); 77 | 78 | if (data == null) { 79 | data = new BallisticProjectileData(instanceID, null, (short) -1, null); 80 | ballisticProjectiles.put(instanceID, data); 81 | 82 | data.destExecute(toProcess, tick); 83 | 84 | data.init(plugin, this); 85 | } else { 86 | data.destExecute(toProcess, tick); 87 | } 88 | } else if (typeID == MissileData.TYPE_ID) { 89 | MissileData data = missiles.get(instanceID); 90 | 91 | if (data == null) { 92 | data = new MissileData(instanceID, null, (short) -1, null); 93 | missiles.put(instanceID, data); 94 | 95 | data.destExecute(toProcess, tick); 96 | 97 | data.init(plugin, this); 98 | } else { 99 | data.destExecute(toProcess, tick); 100 | } 101 | } 102 | } 103 | 104 | @Override 105 | public void processDeletion(byte typeID, short instanceID, MPPlugin plugin, int tick, byte connectionID) { 106 | if (typeID == MovingRayData.TYPE_ID) { 107 | MovingRayData data = movingRays.get(instanceID); 108 | if (data != null) data.delete(); 109 | movingRays.remove(instanceID); 110 | } else if (typeID == BallisticProjectileData.TYPE_ID) { 111 | BallisticProjectileData data = ballisticProjectiles.get(instanceID); 112 | if (data != null) data.delete(); 113 | ballisticProjectiles.remove(instanceID); 114 | } else if (typeID == MissileData.TYPE_ID) { 115 | MissileData data = missiles.get(instanceID); 116 | if (data != null) data.delete(); 117 | missiles.remove(instanceID); 118 | } 119 | } 120 | 121 | 122 | @Override 123 | public void register() { 124 | DataGenManager.registerInboundEntityManager(MovingRayData.TYPE_ID, this); 125 | DataGenManager.registerInboundEntityManager(BallisticProjectileData.TYPE_ID, this); 126 | DataGenManager.registerInboundEntityManager(MissileData.TYPE_ID, this); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/client/combat/entities/VariantDataMap.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables.client.combat.entities; 2 | 3 | import data.scripts.net.data.DataGenManager; 4 | import data.scripts.net.data.packables.entities.ships.VariantData; 5 | import data.scripts.net.data.tables.InboundEntityManager; 6 | import data.scripts.plugins.MPPlugin; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class VariantDataMap implements InboundEntityManager { 12 | private final Map variants; 13 | 14 | public VariantDataMap() { 15 | variants = new HashMap<>(); 16 | } 17 | 18 | @Override 19 | public void processDelta(byte typeID, short instanceID, Map toProcess, MPPlugin plugin, int tick, byte connectionID) { 20 | VariantData data = variants.get(instanceID); 21 | 22 | if (data == null) { 23 | VariantData variantData = new VariantData(instanceID, null, null); 24 | variants.put(instanceID, variantData); 25 | 26 | variantData.destExecute(toProcess, tick); 27 | 28 | variantData.init(plugin, this); 29 | } else { 30 | data.destExecute(toProcess, tick); 31 | } 32 | } 33 | 34 | @Override 35 | public void processDeletion(byte typeID, short instanceID, MPPlugin plugin, int tick, byte connectionID) { 36 | VariantData data = variants.get(instanceID); 37 | 38 | if (data != null) { 39 | data.delete(); 40 | 41 | variants.remove(instanceID); 42 | } 43 | } 44 | 45 | public VariantData find(String shipID) { 46 | for (VariantData variantData : variants.values()) { 47 | if (variantData.getFleetMemberID().equals(shipID)) return variantData; 48 | } 49 | return null; 50 | } 51 | 52 | @Override 53 | public void update(float amount, MPPlugin plugin) { 54 | 55 | } 56 | 57 | public Map getVariants() { 58 | return variants; 59 | } 60 | 61 | @Override 62 | public void register() { 63 | DataGenManager.registerInboundEntityManager(VariantData.TYPE_ID, this); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/client/combat/player/Player.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables.client.combat.player; 2 | 3 | import com.fs.starfarer.api.Global; 4 | import data.scripts.net.data.DataGenManager; 5 | import data.scripts.net.data.InstanceData; 6 | import data.scripts.net.data.packables.metadata.ClientData; 7 | import data.scripts.net.data.packables.metadata.LobbyData; 8 | import data.scripts.net.data.tables.OutboundEntityManager; 9 | import data.scripts.plugins.MPPlugin; 10 | 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | public class Player implements OutboundEntityManager { 17 | private final ClientData player; 18 | private final short connectionID; 19 | 20 | public Player(byte connectionID, MPPlugin plugin) { 21 | this.connectionID = connectionID; 22 | 23 | String username = Global.getSettings().getString("MP_UsernameString"); 24 | if (username.length() > LobbyData.MAX_USERNAME_CHARS) username = username.substring(0, LobbyData.MAX_USERNAME_CHARS); 25 | 26 | player = new ClientData(connectionID, connectionID, plugin, username); 27 | } 28 | 29 | @Override 30 | public Map> getOutbound(byte typeID, float amount, List connectionIDs) { 31 | Map> out = new HashMap<>(); 32 | 33 | Map connectionOutData = new HashMap<>(); 34 | 35 | InstanceData instanceData = player.sourceExecute(amount); 36 | if (instanceData.records != null && !instanceData.records.isEmpty()) { 37 | connectionOutData.put(this.connectionID, instanceData); 38 | } 39 | 40 | for (byte connectionID : connectionIDs) { 41 | out.put(connectionID, connectionOutData); 42 | } 43 | 44 | return out; 45 | } 46 | 47 | @Override 48 | public Set getDeleted(byte typeID, byte connectionID) { 49 | return null; 50 | } 51 | 52 | @Override 53 | public void update(float amount, MPPlugin plugin) { 54 | player.update(amount, this, plugin); 55 | } 56 | 57 | @Override 58 | public void register() { 59 | DataGenManager.registerOutboundEntityManager(ClientData.TYPE_ID, this); 60 | } 61 | 62 | @Override 63 | public PacketType getOutboundPacketType() { 64 | return PacketType.SOCKET; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/client/combat/player/PlayerShip.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables.client.combat.player; 2 | 3 | import cmu.CMUtils; 4 | import com.fs.starfarer.api.Global; 5 | import com.fs.starfarer.api.combat.CombatEngineAPI; 6 | import com.fs.starfarer.api.combat.ShipAPI; 7 | import data.scripts.net.data.DataGenManager; 8 | import data.scripts.net.data.InstanceData; 9 | import data.scripts.net.data.packables.entities.ships.ClientPlayerData; 10 | import data.scripts.net.data.packables.entities.ships.ShipData; 11 | import data.scripts.net.data.packables.metadata.ServerPlayerData; 12 | import data.scripts.net.data.tables.InboundEntityManager; 13 | import data.scripts.net.data.tables.OutboundEntityManager; 14 | import data.scripts.net.data.tables.client.combat.entities.ships.ClientShipTable; 15 | import data.scripts.net.data.tables.server.combat.players.PlayerShips; 16 | import data.scripts.plugins.MPLogger; 17 | import data.scripts.plugins.MPPlugin; 18 | 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.Set; 23 | 24 | public class PlayerShip implements InboundEntityManager, OutboundEntityManager { 25 | 26 | private final ClientPlayerData clientPlayerData; 27 | private final ServerPlayerData serverPlayerData; 28 | private final ClientShipTable clientShipTable; 29 | private final short instanceID; 30 | 31 | private short requestedID = PlayerShips.NULL_SHIP_ID; 32 | 33 | private boolean showNullSwitchError = true; 34 | 35 | public PlayerShip(short instanceID, ClientShipTable clientShipTable) { 36 | this.instanceID = instanceID; 37 | 38 | clientPlayerData = new ClientPlayerData(instanceID, this); 39 | serverPlayerData = new ServerPlayerData(instanceID); 40 | 41 | this.clientShipTable = clientShipTable; 42 | } 43 | 44 | @Override 45 | public void update(float amount, MPPlugin plugin) { 46 | CombatEngineAPI engine = Global.getCombatEngine(); 47 | 48 | clientPlayerData.update(amount, this, plugin); 49 | serverPlayerData.update(amount, this, plugin); 50 | 51 | if (requestedID != PlayerShips.NULL_SHIP_ID && serverPlayerData.getActiveID() == requestedID) { 52 | //switch was executed 53 | requestedID = PlayerShips.NULL_SHIP_ID; 54 | 55 | ShipData data = clientShipTable.getShipTable().array()[serverPlayerData.getActiveID()]; 56 | if (data != null) { 57 | engine.setPlayerShipExternal(data.getShip()); 58 | } 59 | } 60 | 61 | short serverActiveID = serverPlayerData.getActiveID(); 62 | 63 | short currentActiveID = PlayerShips.NULL_SHIP_ID; 64 | if (engine.getPlayerShip() != null) { 65 | Short s = clientShipTable.getShipIDs().get(engine.getPlayerShip()); 66 | if (s != null) currentActiveID = s; 67 | } 68 | 69 | CMUtils.getGuiDebug().putText(PlayerShip.class, "SERVER ACTIVE", "SERVER ACTIVE: " + serverActiveID); 70 | CMUtils.getGuiDebug().putText(PlayerShip.class, "ACTUAL ACTIVE", "ACTUAL ACTIVE: " + currentActiveID); 71 | 72 | // check if server has flagged a switch to a different ship 73 | if (serverActiveID != currentActiveID) { 74 | ShipData data = clientShipTable.getShipTable().array()[serverActiveID]; 75 | 76 | if (data != null && data.getShip() != null) { 77 | engine.setPlayerShipExternal(data.getShip()); 78 | showNullSwitchError = true; 79 | } else if (showNullSwitchError) { 80 | MPLogger.error(PlayerShip.class, "client instructed to switch to null ship"); 81 | showNullSwitchError = false; 82 | } 83 | } 84 | } 85 | 86 | public void requestTransfer(ShipAPI dest) { 87 | Short id = clientShipTable.getShipIDs().get(dest); 88 | 89 | if (id != null) { 90 | clientPlayerData.setRequestedShipID(id); 91 | requestedID = id; 92 | } 93 | } 94 | 95 | @Override 96 | public void register() { 97 | DataGenManager.registerInboundEntityManager(ServerPlayerData.TYPE_ID, this); 98 | DataGenManager.registerOutboundEntityManager(ClientPlayerData.TYPE_ID, this); 99 | } 100 | 101 | @Override 102 | public Map> getOutbound(byte typeID, float amount, List connectionIDs) { 103 | Map> out = new HashMap<>(); 104 | 105 | Map connectionOutData = new HashMap<>(); 106 | 107 | InstanceData instanceData = clientPlayerData.sourceExecute(amount); 108 | if (instanceData.records != null && !instanceData.records.isEmpty()) { 109 | connectionOutData.put(instanceID, instanceData); 110 | } 111 | 112 | for (byte connectionID : connectionIDs) { 113 | out.put(connectionID, connectionOutData); 114 | } 115 | 116 | return out; 117 | } 118 | 119 | @Override 120 | public Set getDeleted(byte typeID, byte connectionID) { 121 | return null; 122 | } 123 | 124 | @Override 125 | public PacketType getOutboundPacketType() { 126 | return PacketType.SOCKET; 127 | } 128 | 129 | public short getActiveShipID() { 130 | return serverPlayerData.getActiveID(); 131 | } 132 | 133 | public ShipAPI getActiveShip() { 134 | if (serverPlayerData.getActiveID() == -1) return null; 135 | return clientShipTable.getShipTable().array()[serverPlayerData.getActiveID()].getShip(); 136 | } 137 | 138 | public ShipAPI getHostShip() { 139 | if (serverPlayerData.getHostID() == -1) return null; 140 | ShipData shipData = clientShipTable.getShipTable().array()[serverPlayerData.getHostID()]; 141 | if (shipData != null) { 142 | return shipData.getShip(); 143 | } 144 | return null; 145 | } 146 | 147 | @Override 148 | public void processDelta(byte typeID, short instanceID, Map toProcess, MPPlugin plugin, int tick, byte connectionID) { 149 | serverPlayerData.destExecute(toProcess, tick); 150 | } 151 | 152 | @Override 153 | public void processDeletion(byte typeID, short instanceID, MPPlugin plugin, int tick, byte connectionID) { 154 | 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/server/ServerCustomDataTable.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables.server; 2 | 3 | public class ServerCustomDataTable { 4 | } 5 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/server/combat/connection/TextChatHost.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables.server.combat.connection; 2 | 3 | import data.scripts.net.data.DataGenManager; 4 | import data.scripts.net.data.InstanceData; 5 | import data.scripts.net.data.packables.metadata.ChatListenData; 6 | import data.scripts.net.data.tables.InboundEntityManager; 7 | import data.scripts.net.data.tables.OutboundEntityManager; 8 | import data.scripts.net.data.tables.server.combat.players.PlayerLobby; 9 | import data.scripts.plugins.MPPlugin; 10 | import data.scripts.plugins.gui.MPChatboxPlugin; 11 | 12 | import java.util.*; 13 | 14 | public class TextChatHost implements InboundEntityManager, OutboundEntityManager { 15 | 16 | private final MPChatboxPlugin chatbox; 17 | private final PlayerLobby playerLobby; 18 | 19 | private final Map listeners = new HashMap<>(); 20 | private final ChatListenData send; 21 | 22 | public TextChatHost(MPChatboxPlugin chatbox, PlayerLobby playerLobby) { 23 | this.chatbox = chatbox; 24 | this.playerLobby = playerLobby; 25 | 26 | send = new ChatListenData(PlayerLobby.HOST_INSTANCE); 27 | } 28 | 29 | @Override 30 | public void update(float amount, MPPlugin plugin) { 31 | List fresh = new ArrayList<>(); 32 | 33 | String hostInput = chatbox.getInput(); 34 | if (hostInput != null) { 35 | fresh.add(new MPChatboxPlugin.ChatEntry(hostInput, playerLobby.getUsernames().get(PlayerLobby.HOST_ID), PlayerLobby.HOST_ID)); 36 | } 37 | 38 | for (short id : listeners.keySet()) { 39 | ChatListenData listener = listeners.get(id); 40 | 41 | List entries = listener.getReceived(); 42 | fresh.addAll(entries); 43 | } 44 | 45 | for (MPChatboxPlugin.ChatEntry chatEntry : fresh) { 46 | String username = playerLobby.getUsernames().get(chatEntry.connectionID); 47 | if (username != null) chatEntry.username = username; 48 | 49 | send.submitChatEntry(chatEntry); 50 | chatbox.addEntry(chatEntry); 51 | } 52 | } 53 | 54 | @Override 55 | public void processDelta(byte typeID, short instanceID, Map toProcess, MPPlugin plugin, int tick, byte connectionID) { 56 | ChatListenData data = listeners.get(instanceID); 57 | if (data == null) { 58 | data = new ChatListenData(instanceID); 59 | listeners.put(instanceID, data); 60 | 61 | data.destExecute(toProcess, tick); 62 | data.init(plugin, this); 63 | } else { 64 | data.destExecute(toProcess, tick); 65 | } 66 | } 67 | 68 | @Override 69 | public void processDeletion(byte typeID, short instanceID, MPPlugin plugin, int tick, byte connectionID) { 70 | listeners.remove(instanceID); 71 | } 72 | 73 | @Override 74 | public Map> getOutbound(byte typeID, float amount, List connectionIDs) { 75 | Map> out = new HashMap<>(); 76 | 77 | Map connectionOutData = new HashMap<>(); 78 | 79 | InstanceData instanceData = send.sourceExecute(amount); 80 | if (!instanceData.records.isEmpty()) { 81 | connectionOutData.put(PlayerLobby.HOST_INSTANCE, instanceData); 82 | } 83 | 84 | for (byte connectionID : connectionIDs) { 85 | out.put(connectionID, connectionOutData); 86 | } 87 | 88 | return out; 89 | } 90 | 91 | @Override 92 | public Set getDeleted(byte typeID, byte connectionID) { 93 | return null; 94 | } 95 | 96 | @Override 97 | public PacketType getOutboundPacketType() { 98 | return PacketType.SOCKET; 99 | } 100 | 101 | @Override 102 | public void register() { 103 | DataGenManager.registerInboundEntityManager(ChatListenData.TYPE_ID, this); 104 | DataGenManager.registerOutboundEntityManager(ChatListenData.TYPE_ID, this); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/server/combat/entities/DamagingExplosionTable.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables.server.combat.entities; 2 | 3 | public class DamagingExplosionTable { 4 | } 5 | -------------------------------------------------------------------------------- /src/data/scripts/net/data/tables/server/combat/players/PlayerLobby.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.data.tables.server.combat.players; 2 | 3 | import com.fs.starfarer.api.Global; 4 | import data.scripts.net.data.DataGenManager; 5 | import data.scripts.net.data.InstanceData; 6 | import data.scripts.net.data.packables.metadata.ClientData; 7 | import data.scripts.net.data.packables.metadata.LobbyData; 8 | import data.scripts.net.data.tables.InboundEntityManager; 9 | import data.scripts.net.data.tables.OutboundEntityManager; 10 | import data.scripts.plugins.MPPlugin; 11 | import data.scripts.plugins.MPServerPlugin; 12 | 13 | import java.util.*; 14 | 15 | public class PlayerLobby implements InboundEntityManager, OutboundEntityManager { 16 | 17 | public static final byte HOST_ID = Byte.MIN_VALUE; 18 | public static final short HOST_INSTANCE = Short.MIN_VALUE; 19 | 20 | private final Map players; 21 | private final ClientData host; 22 | 23 | private final LobbyData lobby; 24 | 25 | private final Map usernames = new HashMap<>(); 26 | 27 | public PlayerLobby(MPServerPlugin serverPlugin, PlayerShips playerShips) { 28 | players = new HashMap<>(); 29 | 30 | String hostUsername = Global.getSettings().getString("MP_UsernameString"); 31 | if (hostUsername.length() > LobbyData.MAX_USERNAME_CHARS) hostUsername = hostUsername.substring(0, LobbyData.MAX_USERNAME_CHARS); 32 | host = new ClientData(HOST_INSTANCE, HOST_ID, serverPlugin, hostUsername); 33 | players.put(HOST_INSTANCE, host); 34 | 35 | usernames.put(HOST_ID, hostUsername); 36 | 37 | lobby = new LobbyData(HOST_INSTANCE, this, playerShips); 38 | } 39 | 40 | @Override 41 | public void processDelta(byte typeID, short instanceID, Map toProcess, MPPlugin plugin, int tick, byte connectionID) { 42 | ClientData data = players.get(instanceID); 43 | 44 | if (data == null) { 45 | data = new ClientData(instanceID, connectionID, null, null); 46 | players.put(instanceID, data); 47 | 48 | data.destExecute(toProcess, tick); 49 | 50 | data.init(plugin, this); 51 | } else { 52 | data.destExecute(toProcess, tick); 53 | } 54 | } 55 | 56 | @Override 57 | public void processDeletion(byte typeID, short instanceID, MPPlugin plugin, int tick, byte connectionID) { 58 | ClientData data = players.get(instanceID); 59 | 60 | if (data != null) { 61 | data.delete(); 62 | 63 | players.remove(instanceID); 64 | } 65 | } 66 | 67 | @Override 68 | public Map> getOutbound(byte typeID, float amount, List connectionIDs) { 69 | Map> out = new HashMap<>(); 70 | 71 | Map connectionOutData = new HashMap<>(); 72 | 73 | InstanceData instanceData = lobby.sourceExecute(amount); 74 | if (instanceData.records != null && !instanceData.records.isEmpty()) { 75 | connectionOutData.put(HOST_INSTANCE, instanceData); 76 | } 77 | 78 | for (byte connectionID : connectionIDs) { 79 | out.put(connectionID, connectionOutData); 80 | } 81 | 82 | return out; 83 | } 84 | 85 | @Override 86 | public Set getDeleted(byte typeID, byte connectionID) { 87 | return null; 88 | } 89 | 90 | @Override 91 | public void update(float amount, MPPlugin plugin) { 92 | host.update(amount, this, plugin); 93 | lobby.update(amount, this, plugin); 94 | for (ClientData clientData : players.values()) { 95 | clientData.update(amount, this, plugin); 96 | 97 | if (clientData.getUsername() != null) { 98 | usernames.put(clientData.getConnectionID(), clientData.getUsername()); 99 | } 100 | } 101 | } 102 | 103 | public Map getPlayers() { 104 | return players; 105 | } 106 | 107 | @Override 108 | public void register() { 109 | DataGenManager.registerInboundEntityManager(ClientData.TYPE_ID, this); 110 | DataGenManager.registerOutboundEntityManager(LobbyData.TYPE_ID, this); 111 | } 112 | 113 | @Override 114 | public PacketType getOutboundPacketType() { 115 | return PacketType.SOCKET; 116 | } 117 | 118 | public Map getUsernames() { 119 | return usernames; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/ByteArrayReader.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io; 2 | 3 | import java.nio.charset.Charset; 4 | import java.util.Arrays; 5 | 6 | public class ByteArrayReader { 7 | private int index; 8 | private final byte[] a; 9 | 10 | public ByteArrayReader(byte[] a) { 11 | this.a = a; 12 | index = 0; 13 | } 14 | 15 | public int readInt() { 16 | if (index + 4 > a.length) { 17 | System.err.println("array read index length exceeded"); 18 | return Integer.MAX_VALUE; 19 | } 20 | 21 | int i = ((a[index] & 0xFF) << 24) | ((a[index + 1] & 0xFF) << 16) | ((a[index + 2] & 0xFF) << 8) | ((a[index + 3] & 0xFF)); 22 | index += 4; 23 | return i; 24 | } 25 | 26 | public byte[] readBytes(int length) { 27 | int i2 = index + length; 28 | byte[] out = Arrays.copyOfRange(a, index, i2); 29 | index = i2; 30 | return out; 31 | } 32 | 33 | public float readFloat() { 34 | return Float.intBitsToFloat(readInt()); 35 | } 36 | 37 | public String readString(int length, Charset charset) { 38 | String out = new String(a, index, length, charset); 39 | index += length; 40 | return out; 41 | } 42 | 43 | public int numBytes() { 44 | return a.length - index; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/ClientDuplex.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io; 2 | 3 | import data.scripts.net.data.InboundData; 4 | import data.scripts.net.data.InstanceData; 5 | import data.scripts.net.data.OutboundData; 6 | 7 | import java.util.HashMap; 8 | import java.util.HashSet; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | public class ClientDuplex { 13 | 14 | private InboundData inbound = new InboundData(); 15 | private OutboundData outboundSocket = new OutboundData((byte) -1); 16 | private OutboundData outboundDatagram = new OutboundData((byte) -1); 17 | 18 | public synchronized InboundData getDeltas() { 19 | InboundData in = inbound; 20 | inbound = new InboundData(); 21 | return in; 22 | } 23 | 24 | public synchronized void updateInbound(InboundData data) { 25 | for (Byte type : data.in.keySet()) { 26 | Map> inboundEntities = inbound.in.get(type); 27 | Map> deltas = data.in.get(type); 28 | 29 | if (inboundEntities == null) { 30 | inboundEntities = new HashMap<>(); 31 | inbound.in.put(type, inboundEntities); 32 | } 33 | 34 | for (Short instance : deltas.keySet()) { 35 | Map p = inboundEntities.get(instance); 36 | Map d = deltas.get(instance); 37 | 38 | if (p == null) { 39 | inboundEntities.put(instance, d); 40 | } else { 41 | for (Byte k : p.keySet()) { 42 | Object delta = d.get(k); 43 | if (delta != null) p.put(k, delta); 44 | } 45 | } 46 | } 47 | } 48 | 49 | for (Byte type : data.deleted.keySet()) { 50 | Set deleted = inbound.deleted.get(type); 51 | Set deltas = data.deleted.get(type); 52 | 53 | if (deleted == null) { 54 | deleted = new HashSet<>(); 55 | inbound.deleted.put(type, deleted); 56 | } 57 | 58 | deleted.addAll(deltas); 59 | } 60 | 61 | inbound.size += data.size;; 62 | } 63 | 64 | public synchronized void updateOutboundSocket(OutboundData bufferData) { 65 | synchronized (outboundSocket.sync) { 66 | ServerDuplex.updateEntities(outboundSocket.getOut(), bufferData.getOut()); 67 | ServerDuplex.updateDeleted(outboundSocket.getDeleted(), bufferData.getDeleted()); 68 | } 69 | } 70 | 71 | public void updateOutboundDatagram(OutboundData bufferData) { 72 | synchronized (outboundDatagram.sync) { 73 | ServerDuplex.updateEntities(outboundDatagram.getOut(), bufferData.getOut()); 74 | ServerDuplex.updateDeleted(outboundDatagram.getDeleted(), bufferData.getDeleted()); 75 | } 76 | } 77 | 78 | public OutboundData getOutboundSocket() { 79 | OutboundData out; 80 | synchronized (outboundSocket.sync) { 81 | out = outboundSocket; 82 | outboundSocket = new OutboundData(new HashMap>(), new HashMap>(), (byte) -1); 83 | } 84 | return out; 85 | } 86 | 87 | public OutboundData getOutboundDatagram() { 88 | OutboundData out; 89 | synchronized (outboundDatagram.sync) { 90 | out = outboundDatagram; 91 | outboundDatagram = new OutboundData(new HashMap>(), new HashMap>(), (byte) -1); 92 | } 93 | return out; 94 | } 95 | 96 | public int getNumSinceTick() { 97 | return 1; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/Clock.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io; 2 | 3 | public class Clock { 4 | private long prev; 5 | private final double timeU; 6 | private double deltaU; 7 | 8 | public Clock(int rate) { 9 | prev = System.nanoTime(); 10 | timeU = 1000000000d / rate; 11 | deltaU = 1d; 12 | } 13 | 14 | public void sleepUntilTick() throws InterruptedException { 15 | long curr; 16 | 17 | while (deltaU < 1d) { 18 | Thread.sleep(0); 19 | 20 | curr = System.nanoTime(); 21 | deltaU += (curr - prev) / timeU; 22 | prev = curr; 23 | } 24 | 25 | deltaU--; 26 | } 27 | 28 | public boolean mark() { 29 | long curr = System.nanoTime(); 30 | deltaU += (curr - prev) / timeU; 31 | prev = curr; 32 | 33 | if (deltaU > 1d) { 34 | deltaU -= 1d; 35 | return true; 36 | } 37 | return false; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/CompressionUtils.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io; 2 | 3 | import java.util.Arrays; 4 | import java.util.zip.DataFormatException; 5 | import java.util.zip.Deflater; 6 | import java.util.zip.Inflater; 7 | 8 | public class CompressionUtils { 9 | 10 | public static byte[] deflate(byte[] bytes) { 11 | Deflater compressor = new Deflater(Deflater.BEST_SPEED); 12 | compressor.setInput(bytes); 13 | compressor.finish(); 14 | byte[] compressed = new byte[bytes.length + 4]; 15 | int length = compressor.deflate(compressed); 16 | compressor.end(); 17 | 18 | return Arrays.copyOfRange(compressed, 0, length); 19 | } 20 | 21 | public static byte[] inflate(byte[] bytes, int size, int sizeCompressed) throws DataFormatException { 22 | Inflater decompressor = new Inflater(); 23 | decompressor.setInput(bytes, 0, sizeCompressed); 24 | byte[] decompressed = new byte[size + 4]; 25 | int decompressedLength = decompressor.inflate(decompressed); 26 | decompressor.end(); 27 | 28 | return Arrays.copyOfRange(decompressed, 0, size); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/MessageContainer.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | 5 | import java.io.IOException; 6 | import java.net.InetSocketAddress; 7 | 8 | public class MessageContainer { 9 | 10 | private final int tick; 11 | private final ByteBuf data; 12 | private final InetSocketAddress dest; 13 | private final byte connectionID; 14 | 15 | public MessageContainer(ByteBuf data, int tick, InetSocketAddress dest, byte connectionID) throws IOException { 16 | this.tick = tick; 17 | this.data = data; 18 | this.dest = dest; 19 | this.connectionID = connectionID; 20 | } 21 | 22 | public boolean isEmpty() { 23 | return data.readableBytes() <= 8; 24 | } 25 | 26 | public ByteBuf getData() { 27 | return data; 28 | } 29 | 30 | public InetSocketAddress getDest() { 31 | return dest; 32 | } 33 | 34 | public byte getConnectionID() { 35 | return connectionID; 36 | } 37 | 38 | public int getTick() { 39 | return tick; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/ServerConnectionManager.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io; 2 | 3 | import com.fs.starfarer.api.Global; 4 | import data.scripts.net.io.tcp.server.SocketServer; 5 | import data.scripts.net.io.udp.server.DatagramServer; 6 | import data.scripts.plugins.MPServerPlugin; 7 | import org.lazywizard.console.Console; 8 | 9 | import java.io.IOException; 10 | import java.net.InetSocketAddress; 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | public class ServerConnectionManager implements Runnable { 17 | public static final int MP_MAX_CONNECTIONS = Global.getSettings().getInt("mpMaxConnections"); 18 | 19 | // public final static int PORT = Global.getSettings().getInt("mpLocalPortTCP"); 20 | public static final int TICK_RATE = Global.getSettings().getInt("mpServerTickRate"); 21 | 22 | private final ServerDuplex duplex; 23 | private final MPServerPlugin serverPlugin; 24 | private boolean active; 25 | 26 | private final DatagramServer datagramServer; 27 | private final Thread datagram; 28 | 29 | private final SocketServer socketServer; 30 | private final Thread socket; 31 | 32 | private final Map serverConnectionWrappers; 33 | private byte inc = 1; 34 | 35 | private int tick; 36 | private final Clock clock; 37 | 38 | public ServerConnectionManager(MPServerPlugin serverPlugin, int port) { 39 | this.serverPlugin = serverPlugin; 40 | duplex = new ServerDuplex(); 41 | active = true; 42 | 43 | serverConnectionWrappers = new HashMap<>(); 44 | 45 | socketServer = new SocketServer(port, this); 46 | socket = new Thread(socketServer, "SOCKET_SERVER_THREAD"); 47 | 48 | datagramServer = new DatagramServer(port, this); 49 | datagram = new Thread(datagramServer, "DATAGRAM_SERVER_THREAD"); 50 | 51 | tick = 0; 52 | clock = new Clock(TICK_RATE); 53 | 54 | } 55 | 56 | @Override 57 | public void run() { 58 | Console.showMessage("Starting main server..."); 59 | 60 | socket.start(); 61 | datagram.start(); 62 | 63 | try { 64 | while (active) { 65 | clock.sleepUntilTick(); 66 | tick++; 67 | 68 | List socketMessages = getSocketMessages(); 69 | if (!socketMessages.isEmpty()) socketServer.addMessages(socketMessages); 70 | 71 | List datagramMessages = getDatagramMessages(); 72 | if (!datagramMessages.isEmpty()) datagramServer.addMessages(datagramMessages); 73 | } 74 | } catch (InterruptedException | IOException e) { 75 | e.printStackTrace(); 76 | } finally { 77 | stop(); 78 | } 79 | } 80 | 81 | public List getSocketMessages() throws IOException { 82 | List output = new ArrayList<>(); 83 | 84 | for (ServerConnectionWrapper connection : serverConnectionWrappers.values()) { 85 | List messages = connection.getSocketMessages(); 86 | if (messages != null) output.addAll(messages); 87 | } 88 | 89 | return output; 90 | } 91 | 92 | public List getDatagramMessages() throws IOException { 93 | List output = new ArrayList<>(); 94 | 95 | for (ServerConnectionWrapper connection : serverConnectionWrappers.values()) { 96 | List messages = connection.getDatagrams(); 97 | if (messages != null) output.addAll(messages); 98 | } 99 | 100 | return output; 101 | } 102 | 103 | public ServerConnectionWrapper getConnection(byte connectionID) { 104 | for (byte id : serverConnectionWrappers.keySet()) { 105 | ServerConnectionWrapper wrapper = serverConnectionWrappers.get(id); 106 | if (id == connectionID) { 107 | return wrapper; 108 | } 109 | } 110 | return null; 111 | } 112 | 113 | public ServerConnectionWrapper getNewConnection(InetSocketAddress remoteAddress) { 114 | if (remoteAddress == null) return null; 115 | 116 | synchronized (serverConnectionWrappers) { 117 | if (serverConnectionWrappers.size() >= MP_MAX_CONNECTIONS) return null; 118 | } 119 | 120 | byte id = inc; 121 | ServerConnectionWrapper serverConnectionWrapper = new ServerConnectionWrapper(this, id, remoteAddress, serverPlugin); 122 | inc++; 123 | 124 | synchronized (serverConnectionWrappers) { 125 | serverConnectionWrappers.put(id, serverConnectionWrapper); 126 | } 127 | 128 | return serverConnectionWrapper; 129 | } 130 | 131 | public void removeConnection(byte id) { 132 | synchronized (serverConnectionWrappers) { 133 | serverConnectionWrappers.remove(id); 134 | } 135 | duplex.removeConnection(id); 136 | } 137 | 138 | public void stop() { 139 | active = false; 140 | 141 | socketServer.stop(); 142 | datagramServer.stop(); 143 | 144 | System.out.println("STOPPING PRIMARY SERVER THREAD"); 145 | } 146 | 147 | public synchronized int getTick() { 148 | return tick; 149 | } 150 | 151 | public synchronized ServerDuplex getDuplex() { 152 | return duplex; 153 | } 154 | 155 | public synchronized boolean isActive() { 156 | return active; 157 | } 158 | 159 | public MPServerPlugin getServerPlugin() { 160 | return serverPlugin; 161 | } 162 | 163 | public Map getServerConnectionWrappers() { 164 | return serverConnectionWrappers; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/Unpacked.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io; 2 | 3 | import data.scripts.net.data.InboundData; 4 | import io.netty.buffer.ByteBuf; 5 | 6 | import java.net.InetSocketAddress; 7 | 8 | public class Unpacked { 9 | private final InboundData unpacked; 10 | private final int tick; 11 | 12 | private final InetSocketAddress sender; 13 | private final InetSocketAddress recipient; 14 | private final byte connectionID; 15 | private final int size; 16 | 17 | public Unpacked(ByteBuf data, InetSocketAddress sender, InetSocketAddress recipient) { 18 | this.sender = sender; 19 | this.recipient = recipient; 20 | 21 | size = data.readableBytes(); 22 | 23 | tick = data.readInt(); 24 | connectionID = data.readByte(); 25 | 26 | InboundData m; 27 | try { 28 | m = BaseConnectionWrapper.readBuffer(data); 29 | } catch (Exception e) { 30 | System.err.println("Decode failed for buffer with size " + size + " from " + connectionID); 31 | e.printStackTrace(); 32 | m = new InboundData(); 33 | } 34 | 35 | unpacked = m; 36 | } 37 | 38 | public InboundData getUnpacked() { 39 | return unpacked; 40 | } 41 | 42 | public int getTick() { 43 | return tick; 44 | } 45 | 46 | public InetSocketAddress getRecipient() { 47 | return recipient; 48 | } 49 | 50 | public InetSocketAddress getSender() { 51 | return sender; 52 | } 53 | 54 | public byte getConnectionID() { 55 | return connectionID; 56 | } 57 | 58 | public int getSize() { 59 | return size; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/tcp/BufferUnpacker.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.tcp; 2 | 3 | import data.scripts.net.io.Unpacked; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.ByteToMessageDecoder; 7 | 8 | import java.net.InetSocketAddress; 9 | import java.util.List; 10 | 11 | public class BufferUnpacker extends ByteToMessageDecoder { 12 | @Override 13 | protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List out) { 14 | Unpacked result = new Unpacked( 15 | in, 16 | (InetSocketAddress) channelHandlerContext.channel().remoteAddress(), 17 | (InetSocketAddress) channelHandlerContext.channel().localAddress() 18 | ); 19 | 20 | out.add(result); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/tcp/MessageContainerDecoder.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.tcp; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.ReplayingDecoder; 6 | 7 | import java.util.List; 8 | 9 | public class MessageContainerDecoder extends ReplayingDecoder { 10 | @Override 11 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { 12 | out.add(in.readBytes(in.readInt())); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/tcp/MessageContainerEncoder.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.tcp; 2 | 3 | import data.scripts.net.io.MessageContainer; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.MessageToByteEncoder; 7 | 8 | public class MessageContainerEncoder extends MessageToByteEncoder { 9 | 10 | @Override 11 | protected void encode(ChannelHandlerContext ctx, MessageContainer msg, ByteBuf out) throws Exception { 12 | // encode length of buffer so completeness can be checked when reconstructed 13 | ByteBuf data = msg.getData(); 14 | int length = data.readableBytes(); 15 | 16 | out.writeInt(length); 17 | out.writeBytes(data); 18 | 19 | data.release(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/tcp/client/ClientChannelHandler.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.tcp.client; 2 | 3 | import data.scripts.net.data.InboundData; 4 | import data.scripts.net.io.ClientConnectionWrapper; 5 | import data.scripts.net.io.Unpacked; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | import org.lazywizard.console.Console; 9 | 10 | /** 11 | * Main logic for handling network packet data 12 | */ 13 | public class ClientChannelHandler extends SimpleChannelInboundHandler { 14 | private final ClientConnectionWrapper connection; 15 | 16 | public ClientChannelHandler(ClientConnectionWrapper connection) { 17 | this.connection = connection; 18 | } 19 | 20 | @Override 21 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 22 | cause.printStackTrace(); 23 | connection.stop(); 24 | } 25 | 26 | @Override 27 | public void channelActive(ChannelHandlerContext ctx) { 28 | Console.showMessage("Channel active on client"); 29 | } 30 | 31 | @Override 32 | public void channelRead0(ChannelHandlerContext ctx, Unpacked unpacked) { 33 | int serverTick = unpacked.getTick(); 34 | //Console.showMessage("Received TCP unpacked with tick: " + serverTick); 35 | 36 | InboundData entities = unpacked.getUnpacked(); 37 | 38 | connection.updateInbound(entities, -1); 39 | } 40 | 41 | @Override 42 | public void handlerAdded(ChannelHandlerContext ctx) { 43 | Console.showMessage("Client channel handler added"); 44 | } 45 | 46 | @Override 47 | public void handlerRemoved(ChannelHandlerContext ctx) { 48 | Console.showMessage("Client channel handler removed"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/tcp/client/SocketClient.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.tcp.client; 2 | 3 | import com.fs.starfarer.api.Global; 4 | import data.scripts.net.io.ClientConnectionWrapper; 5 | import data.scripts.net.io.Clock; 6 | import data.scripts.net.io.MessageContainer; 7 | import data.scripts.net.io.tcp.BufferUnpacker; 8 | import data.scripts.net.io.tcp.MessageContainerDecoder; 9 | import data.scripts.net.io.tcp.MessageContainerEncoder; 10 | import io.netty.bootstrap.Bootstrap; 11 | import io.netty.channel.*; 12 | import io.netty.channel.nio.NioEventLoopGroup; 13 | import io.netty.channel.socket.SocketChannel; 14 | import io.netty.channel.socket.nio.NioSocketChannel; 15 | import org.lazywizard.console.Console; 16 | 17 | import java.net.InetSocketAddress; 18 | import java.util.List; 19 | 20 | public class SocketClient implements Runnable { 21 | public static final int TICK_RATE = Global.getSettings().getInt("mpClientTickrate"); 22 | 23 | private final String host; 24 | private final int port; 25 | private InetSocketAddress local; 26 | 27 | private EventLoopGroup workerGroup; 28 | private final ClientConnectionWrapper connection; 29 | private Channel channel; 30 | 31 | private final Clock clock; 32 | 33 | public SocketClient(String host, int port, ClientConnectionWrapper connection) { 34 | this.host = host; 35 | this.port = port; 36 | this.connection = connection; 37 | 38 | clock = new Clock(TICK_RATE); 39 | } 40 | 41 | public InetSocketAddress getLocal() { 42 | return local; 43 | } 44 | 45 | @Override 46 | public void run() { 47 | runClient(); 48 | } 49 | 50 | public void runClient() { 51 | try { 52 | ChannelFuture channelFuture = start(); 53 | ChannelFuture closeFuture = channelFuture.channel().closeFuture(); 54 | 55 | Console.showMessage("TCP socket active on port " + port); 56 | 57 | // LOOP WRITE OPERATIONS ONLY 58 | // Incoming messages handled by inbound channel adapter 59 | while (connection.getConnectionState() != ClientConnectionWrapper.ConnectionState.CLOSED) { 60 | clock.sleepUntilTick(); 61 | 62 | List containers = connection.getSocketMessages(); 63 | if (containers == null || containers.isEmpty()) continue; 64 | 65 | for (MessageContainer message : containers) { 66 | write(message); 67 | } 68 | } 69 | 70 | // Wait for channel to close 71 | closeFuture.sync(); 72 | } catch (Throwable e) { 73 | e.printStackTrace(); 74 | } finally { 75 | System.err.println("CLOSING SOCKET THREAD"); 76 | connection.stop(); 77 | } 78 | } 79 | 80 | private void write(Object msg) throws InterruptedException { 81 | channel.writeAndFlush(msg).sync(); 82 | } 83 | 84 | private ChannelFuture start() throws InterruptedException { 85 | workerGroup = new NioEventLoopGroup(); 86 | 87 | Bootstrap bootstrap = new Bootstrap(); 88 | bootstrap.group(workerGroup); 89 | bootstrap.channel(NioSocketChannel.class); 90 | bootstrap.option(ChannelOption.SO_KEEPALIVE, true); 91 | bootstrap.handler(new ChannelInitializer() { 92 | @Override 93 | protected void initChannel(SocketChannel socketChannel) throws Exception { 94 | socketChannel.pipeline().addLast( 95 | new MessageContainerEncoder(), 96 | new MessageContainerDecoder(), 97 | new BufferUnpacker(), 98 | new ClientChannelHandler(connection) 99 | ); 100 | } 101 | }); 102 | 103 | // Get channel after connected socket 104 | 105 | String h = host.equals("localhost") ? "127.0.0.1" : host; 106 | int p = host.equals("localhost") ? 8080 : port; 107 | ChannelFuture channelFuture = bootstrap.connect(h, p).sync(); 108 | channel = channelFuture.channel(); 109 | local = (InetSocketAddress) channel.localAddress(); 110 | 111 | return channelFuture; 112 | } 113 | 114 | public void stop() { 115 | if (channel != null) channel.close(); 116 | if (workerGroup != null) workerGroup.shutdownGracefully(); 117 | } 118 | 119 | public Channel getChannel() { 120 | return channel; 121 | } 122 | 123 | public int getLocalPort() { 124 | return ((InetSocketAddress) channel.localAddress()).getPort(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/tcp/server/ServerChannelHandler.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.tcp.server; 2 | 3 | import data.scripts.net.data.InboundData; 4 | import data.scripts.net.io.ServerConnectionWrapper; 5 | import data.scripts.net.io.Unpacked; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | import org.lazywizard.console.Console; 9 | 10 | import java.io.IOException; 11 | 12 | public class ServerChannelHandler extends SimpleChannelInboundHandler { 13 | private final ServerConnectionWrapper serverConnectionWrapper; 14 | private final SocketServer socketServer; 15 | 16 | public ServerChannelHandler(ServerConnectionWrapper serverConnectionWrapper, SocketServer socketServer) { 17 | this.serverConnectionWrapper = serverConnectionWrapper; 18 | this.socketServer = socketServer; 19 | } 20 | 21 | @Override 22 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 23 | cause.printStackTrace(); 24 | serverConnectionWrapper.stop(); 25 | } 26 | 27 | @Override 28 | public void handlerAdded(ChannelHandlerContext ctx) { 29 | Console.showMessage("Server channel handler added"); 30 | } 31 | 32 | @Override 33 | public void handlerRemoved(ChannelHandlerContext ctx) { 34 | Console.showMessage("Server channel handler removed"); 35 | } 36 | 37 | @Override 38 | public void channelRead0(ChannelHandlerContext ctx, Unpacked unpacked) { 39 | int clientTick = unpacked.getTick(); 40 | //Console.showMessage("Received TCP client tick notice: " + clientTick); 41 | 42 | InboundData entities = unpacked.getUnpacked(); 43 | 44 | serverConnectionWrapper.updateInbound(entities); 45 | } 46 | 47 | /** 48 | * Called once when TCP connection is active 49 | * @param ctx context 50 | */ 51 | @Override 52 | public void channelActive(final ChannelHandlerContext ctx) throws IOException, InterruptedException { 53 | Console.showMessage("Channel active on server"); 54 | 55 | socketServer.getChannelGroup().add(ctx.channel()); 56 | } 57 | 58 | @Override 59 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 60 | } 61 | 62 | @Override 63 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 64 | } 65 | 66 | /** 67 | * Called once read is complete. Is used to wait and send next packet. 68 | * @param ctx context 69 | */ 70 | @Override 71 | public void channelReadComplete(final ChannelHandlerContext ctx) throws IOException, InterruptedException { 72 | 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/tcp/server/SocketChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.tcp.server; 2 | 3 | import data.scripts.net.io.ServerConnectionManager; 4 | import data.scripts.net.io.ServerConnectionWrapper; 5 | import data.scripts.net.io.tcp.BufferUnpacker; 6 | import data.scripts.net.io.tcp.MessageContainerDecoder; 7 | import data.scripts.net.io.tcp.MessageContainerEncoder; 8 | import io.netty.channel.ChannelInitializer; 9 | import io.netty.channel.socket.SocketChannel; 10 | 11 | public class SocketChannelInitializer extends ChannelInitializer { 12 | private final SocketServer socketServer; 13 | private final ServerConnectionManager serverConnectionManager; 14 | 15 | public SocketChannelInitializer(SocketServer socketServer, ServerConnectionManager serverConnectionManager) { 16 | this.socketServer = socketServer; 17 | this.serverConnectionManager = serverConnectionManager; 18 | } 19 | 20 | @Override 21 | protected void initChannel(SocketChannel socketChannel) throws InterruptedException { 22 | ServerConnectionWrapper connection = serverConnectionManager.getNewConnection(socketChannel.remoteAddress()); 23 | 24 | if (connection == null) { 25 | throw new InterruptedException("Channel connection refused: max connections exceeded"); 26 | } 27 | 28 | socketChannel.pipeline().addLast( 29 | new MessageContainerEncoder(), 30 | new MessageContainerDecoder(), 31 | new BufferUnpacker(), 32 | new ServerChannelHandler(connection, socketServer) 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/tcp/server/SocketServer.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.tcp.server; 2 | 3 | import com.fs.starfarer.api.Global; 4 | import data.scripts.net.io.MessageContainer; 5 | import data.scripts.net.io.ServerConnectionManager; 6 | import io.netty.bootstrap.ServerBootstrap; 7 | import io.netty.channel.Channel; 8 | import io.netty.channel.ChannelFuture; 9 | import io.netty.channel.ChannelOption; 10 | import io.netty.channel.EventLoopGroup; 11 | import io.netty.channel.group.ChannelGroup; 12 | import io.netty.channel.group.ChannelMatcher; 13 | import io.netty.channel.group.DefaultChannelGroup; 14 | import io.netty.channel.nio.NioEventLoopGroup; 15 | import io.netty.channel.socket.nio.NioServerSocketChannel; 16 | import io.netty.util.concurrent.GlobalEventExecutor; 17 | import org.lazywizard.console.Console; 18 | 19 | import java.net.InetSocketAddress; 20 | import java.net.SocketAddress; 21 | import java.util.List; 22 | import java.util.concurrent.ArrayBlockingQueue; 23 | 24 | public class SocketServer implements Runnable { 25 | public static final int MAX_QUEUE_SIZE = Global.getSettings().getInt("mpSocketQueueLimit"); 26 | 27 | private final int port; 28 | private final ServerConnectionManager connectionManager; 29 | private final ArrayBlockingQueue messageQueue; 30 | 31 | private EventLoopGroup bossGroup; 32 | private EventLoopGroup workerGroup; 33 | private Channel serverChannel; 34 | private final DefaultChannelGroup channelGroup; 35 | 36 | public SocketServer(int port, ServerConnectionManager connectionManager) { 37 | this.port = port; 38 | this.connectionManager = connectionManager; 39 | 40 | messageQueue = new ArrayBlockingQueue<>(MAX_QUEUE_SIZE); 41 | 42 | channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); 43 | } 44 | 45 | @Override 46 | public void run() { 47 | Console.showMessage("Running TCP server on port " + port + " at " + ServerConnectionManager.TICK_RATE + "Hz"); 48 | 49 | ChannelFuture channelFuture = start(); 50 | 51 | ChannelFuture closeFuture = channelFuture.channel().closeFuture(); 52 | 53 | try { 54 | while (connectionManager.isActive()) { 55 | while (!messageQueue.isEmpty()) { 56 | final MessageContainer message = messageQueue.poll(); 57 | 58 | channelGroup.writeAndFlush(message, new ChannelMatcher() { 59 | @Override 60 | public boolean matches(Channel channel) { 61 | SocketAddress address = channel.remoteAddress(); 62 | InetSocketAddress messageAddress = message.getDest(); 63 | 64 | return address == messageAddress; 65 | } 66 | }); 67 | } 68 | } 69 | 70 | closeFuture.sync(); 71 | } catch (Throwable e) { 72 | e.printStackTrace(); 73 | } finally { 74 | System.err.println("CLOSING SOCKET THREAD"); 75 | connectionManager.stop(); 76 | } 77 | } 78 | 79 | private ChannelFuture start() { 80 | bossGroup = new NioEventLoopGroup(); 81 | workerGroup = new NioEventLoopGroup(); 82 | 83 | final ServerBootstrap server = new ServerBootstrap(); 84 | server.group(bossGroup, workerGroup) 85 | .channel(NioServerSocketChannel.class) 86 | .childHandler(new SocketChannelInitializer(this, connectionManager)) 87 | .option(ChannelOption.SO_BACKLOG, 128) 88 | .childOption(ChannelOption.SO_KEEPALIVE, false); 89 | 90 | // Bind to TCP port and wait for channel from ready socket 91 | ChannelFuture future = server.bind(port).syncUninterruptibly(); 92 | serverChannel = future.channel(); 93 | 94 | return future; 95 | } 96 | 97 | public void addMessages(List messages) { 98 | for (MessageContainer messageContainer : messages) { 99 | if (messageQueue.remainingCapacity() == 0) { 100 | messageQueue.poll(); 101 | } 102 | 103 | messageQueue.offer(messageContainer); 104 | } 105 | } 106 | 107 | public void stop() { 108 | if (serverChannel != null) serverChannel.close(); 109 | if (workerGroup != null) workerGroup.shutdownGracefully(); 110 | if (bossGroup != null) bossGroup.shutdownGracefully(); 111 | 112 | System.err.println("CLOSED SOCKET THREAD SYNC GRACEFULLY"); 113 | } 114 | 115 | public ChannelGroup getChannelGroup() { 116 | return channelGroup; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/udp/DatagramDecoder.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.udp; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.socket.DatagramPacket; 5 | import io.netty.handler.codec.MessageToMessageDecoder; 6 | 7 | import java.util.List; 8 | 9 | public class DatagramDecoder extends MessageToMessageDecoder { 10 | 11 | @Override 12 | protected void decode(ChannelHandlerContext context, DatagramPacket in, List out) throws Exception { 13 | DatagramUtils.Decompressed decompressed = DatagramUtils.read(in); 14 | 15 | if (decompressed.data.length == 0) { 16 | context.flush(); 17 | return; 18 | } 19 | 20 | out.add(decompressed); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/udp/DatagramUnpacker.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.udp; 2 | 3 | import data.scripts.net.io.Unpacked; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.PooledByteBufAllocator; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.handler.codec.MessageToMessageDecoder; 8 | 9 | import java.net.InetSocketAddress; 10 | import java.util.List; 11 | 12 | public class DatagramUnpacker extends MessageToMessageDecoder { 13 | @Override 14 | protected void decode(ChannelHandlerContext channelHandlerContext, DatagramUtils.Decompressed in, List out) throws Exception { 15 | ByteBuf data = PooledByteBufAllocator.DEFAULT.buffer(in.data.length); 16 | data.writeBytes(in.data); 17 | 18 | try { 19 | Unpacked result = new Unpacked( 20 | data, 21 | (InetSocketAddress) channelHandlerContext.channel().remoteAddress(), 22 | (InetSocketAddress) channelHandlerContext.channel().localAddress() 23 | ); 24 | 25 | out.add(result); 26 | } catch (Throwable t) { 27 | t.printStackTrace(); 28 | channelHandlerContext.flush(); 29 | } finally { 30 | data.release(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/udp/DatagramUtils.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.udp; 2 | 3 | import data.scripts.net.io.MessageContainer; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.PooledByteBufAllocator; 6 | import io.netty.channel.Channel; 7 | import io.netty.channel.socket.DatagramPacket; 8 | 9 | import java.net.InetSocketAddress; 10 | import java.util.zip.DataFormatException; 11 | 12 | public class DatagramUtils { 13 | 14 | public static SizeData write(Channel channel, MessageContainer message, InetSocketAddress dest, byte connectionID) throws InterruptedException { 15 | ByteBuf buf = message.getData(); 16 | try { 17 | if (buf.readableBytes() <= 4) { 18 | channel.flush(); 19 | return null; 20 | } 21 | 22 | SizeData sizeData = new SizeData(); 23 | 24 | byte[] bytes = new byte[buf.readableBytes()]; 25 | sizeData.size = bytes.length; 26 | buf.readBytes(bytes); 27 | 28 | ByteBuf out = PooledByteBufAllocator.DEFAULT.buffer(bytes.length + 4); 29 | 30 | out.writeInt(sizeData.size); 31 | 32 | out.writeBytes(bytes); 33 | 34 | channel.writeAndFlush(new DatagramPacket(out, dest)).sync(); 35 | 36 | return sizeData; 37 | } finally { 38 | buf.release(); 39 | } 40 | } 41 | 42 | public static Decompressed read(DatagramPacket in) throws DataFormatException { 43 | ByteBuf content = in.content(); 44 | 45 | int size = content.readInt(); 46 | 47 | if (size == 0) return new Decompressed(); 48 | 49 | byte[] bytes = new byte[size]; 50 | content.readBytes(bytes); 51 | 52 | Decompressed out = new Decompressed(); 53 | out.data = bytes; 54 | 55 | return out; 56 | } 57 | 58 | public static class SizeData { 59 | public int size; 60 | public int sizeCompressed; 61 | } 62 | 63 | public static class Decompressed { 64 | public byte[] data; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/udp/client/ClientInboundHandler.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.udp.client; 2 | 3 | import data.scripts.net.data.InboundData; 4 | import data.scripts.net.io.ClientConnectionWrapper; 5 | import data.scripts.net.io.Unpacked; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | 9 | public class ClientInboundHandler extends SimpleChannelInboundHandler { 10 | private final ClientConnectionWrapper connection; 11 | 12 | public ClientInboundHandler(ClientConnectionWrapper connection) { 13 | this.connection = connection; 14 | } 15 | 16 | @Override 17 | protected void channelRead0(ChannelHandlerContext ctx, Unpacked in) throws Exception { 18 | int serverTick = in.getTick(); 19 | //Console.showMessage("Received UDP unpacked with tick: " + serverTick); 20 | 21 | InboundData entities = in.getUnpacked(); 22 | entities.setSize(in.getSize()); 23 | 24 | connection.updateInbound(entities, serverTick); 25 | } 26 | 27 | @Override 28 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 29 | if (cause instanceof ArrayIndexOutOfBoundsException) { 30 | System.err.println("Malformed packet caught"); 31 | ctx.flush(); 32 | } else { 33 | System.err.println("Error caught in datagram channel: " + cause.getMessage()); 34 | cause.printStackTrace(); 35 | ctx.close(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/udp/client/DatagramClient.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.udp.client; 2 | 3 | import cmu.CMUtils; 4 | import cmu.plugins.debug.DebugGraphContainer; 5 | import com.fs.starfarer.api.Global; 6 | import data.scripts.net.io.*; 7 | import data.scripts.net.io.udp.DatagramDecoder; 8 | import data.scripts.net.io.udp.DatagramUnpacker; 9 | import data.scripts.net.io.udp.DatagramUtils; 10 | import io.netty.bootstrap.Bootstrap; 11 | import io.netty.channel.ChannelFuture; 12 | import io.netty.channel.ChannelInitializer; 13 | import io.netty.channel.EventLoopGroup; 14 | import io.netty.channel.nio.NioEventLoopGroup; 15 | import io.netty.channel.socket.nio.NioDatagramChannel; 16 | import org.lazywizard.console.Console; 17 | 18 | import java.net.InetSocketAddress; 19 | import java.util.List; 20 | 21 | public class DatagramClient implements Runnable { 22 | public static final int TICK_RATE = Global.getSettings().getInt("mpClientTickrate"); 23 | 24 | private final int remotePort; 25 | private final String host; 26 | private final int localPort; 27 | private final ClientConnectionWrapper connection; 28 | 29 | private EventLoopGroup workerGroup; 30 | 31 | private NioDatagramChannel channel; 32 | 33 | private final Clock clock; 34 | private boolean running; 35 | 36 | private final DebugGraphContainer dataGraph; 37 | // private final DebugGraphContainer dataGraphCompressed; 38 | // private final DebugGraphContainer dataGraphRatio; 39 | 40 | 41 | public DatagramClient(String host, int remotePort, int localPort, ClientConnectionWrapper connection) { 42 | this.host = host; 43 | this.remotePort = remotePort; 44 | this.localPort = localPort; 45 | this.connection = connection; 46 | 47 | clock = new Clock(TICK_RATE); 48 | 49 | dataGraph = new DebugGraphContainer("Outbound Packet Size", ServerConnectionManager.TICK_RATE, 60f); 50 | // dataGraphCompressed = new DebugGraphContainer("Compressed Bytes Out", ServerConnectionManager.TICK_RATE, 50f); 51 | // dataGraphRatio = new DebugGraphContainer("Compression Ratio", ServerConnectionManager.TICK_RATE, 50f); 52 | 53 | 54 | running = false; 55 | } 56 | 57 | @Override 58 | public void run() { 59 | runClient(); 60 | } 61 | 62 | public void runClient() { 63 | running = true; 64 | 65 | InetSocketAddress remoteAddress = new InetSocketAddress(host, remotePort); 66 | 67 | ChannelFuture future = start(); 68 | ChannelFuture closeFuture = future.channel().closeFuture(); 69 | Console.showMessage("UDP channel active on port " + remotePort + " at " + TICK_RATE + "Hz"); 70 | 71 | try { 72 | // LOOP WRITE OPERATIONS ONLY 73 | // Incoming messages handled by inbound channel adapter 74 | while (connection.getConnectionState() != ClientConnectionWrapper.ConnectionState.CLOSED) { 75 | int size = 0; 76 | int sizeCompressed = 0; 77 | 78 | clock.sleepUntilTick(); 79 | 80 | List messages = connection.getDatagrams(); 81 | 82 | if (messages == null || messages.isEmpty()) continue; 83 | 84 | for (MessageContainer message : messages) { 85 | DatagramUtils.SizeData sizeData = DatagramUtils.write(channel, message, remoteAddress, message.getConnectionID()); 86 | if (sizeData != null) { 87 | size += sizeData.size; 88 | sizeCompressed += sizeData.sizeCompressed; 89 | } 90 | } 91 | 92 | dataGraph.increment(size); 93 | CMUtils.getGuiDebug().putContainer(DatagramClient.class, "dataGraph", dataGraph); 94 | // dataGraphCompressed.increment(sizeCompressed); 95 | // CMUtils.getGuiDebug().putContainer(DatagramClient.class, "dataGraphCompressed", dataGraphCompressed); 96 | // dataGraphRatio.increment(100f * ((float) sizeCompressed / size)); 97 | // CMUtils.getGuiDebug().putContainer(DatagramClient.class, "dataGraphRatio", dataGraphRatio); 98 | } 99 | 100 | closeFuture.sync(); 101 | stop(); 102 | } catch (Throwable e) { 103 | e.printStackTrace(); 104 | stop(); 105 | connection.setConnectionState(BaseConnectionWrapper.ConnectionState.CLOSED); 106 | } 107 | } 108 | 109 | private ChannelFuture start() { 110 | workerGroup = new NioEventLoopGroup(); 111 | 112 | Bootstrap bootstrap = new Bootstrap(); 113 | bootstrap.group(workerGroup); 114 | bootstrap.channel(NioDatagramChannel.class); 115 | bootstrap.handler(new ChannelInitializer() { 116 | @Override 117 | protected void initChannel(NioDatagramChannel datagramChannel) { 118 | datagramChannel.pipeline().addLast( 119 | new DatagramDecoder(), 120 | new DatagramUnpacker(), 121 | new ClientInboundHandler(connection) 122 | //new DatagramEncoder() 123 | ); 124 | } 125 | }); 126 | 127 | ChannelFuture channelFuture = bootstrap.bind(localPort).syncUninterruptibly(); 128 | channelFuture.syncUninterruptibly(); 129 | 130 | channel = (NioDatagramChannel) channelFuture.channel(); 131 | 132 | return channelFuture; 133 | } 134 | 135 | public void stop() { 136 | if (channel != null) channel.close(); 137 | if (workerGroup != null) workerGroup.shutdownGracefully(); 138 | running = false; 139 | } 140 | 141 | public boolean isRunning() { 142 | return running; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/udp/server/DatagramChannelInitializer.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.udp.server; 2 | 3 | import data.scripts.net.io.ServerConnectionManager; 4 | import data.scripts.net.io.udp.DatagramDecoder; 5 | import data.scripts.net.io.udp.DatagramUnpacker; 6 | import io.netty.channel.ChannelInitializer; 7 | import io.netty.channel.socket.nio.NioDatagramChannel; 8 | 9 | public class DatagramChannelInitializer extends ChannelInitializer { 10 | private final DatagramServer datagramServer; 11 | private final ServerConnectionManager serverConnectionManager; 12 | 13 | public DatagramChannelInitializer(DatagramServer datagramServer, ServerConnectionManager serverConnectionManager) { 14 | this.datagramServer = datagramServer; 15 | this.serverConnectionManager = serverConnectionManager; 16 | } 17 | 18 | @Override 19 | protected void initChannel(NioDatagramChannel datagramChannel) { 20 | datagramChannel.pipeline().addLast( 21 | new DatagramDecoder(), 22 | new DatagramUnpacker(), 23 | new ServerInboundHandler(serverConnectionManager) 24 | //new DatagramEncoder() 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/udp/server/DatagramServer.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.udp.server; 2 | 3 | import cmu.CMUtils; 4 | import cmu.plugins.debug.DebugGraphContainer; 5 | import com.fs.starfarer.api.Global; 6 | import data.scripts.net.io.Clock; 7 | import data.scripts.net.io.MessageContainer; 8 | import data.scripts.net.io.ServerConnectionManager; 9 | import data.scripts.net.io.udp.DatagramUtils; 10 | import io.netty.bootstrap.Bootstrap; 11 | import io.netty.channel.Channel; 12 | import io.netty.channel.ChannelFuture; 13 | import io.netty.channel.EventLoopGroup; 14 | import io.netty.channel.nio.NioEventLoopGroup; 15 | import io.netty.channel.socket.nio.NioDatagramChannel; 16 | import org.lazywizard.console.Console; 17 | 18 | import java.net.InetSocketAddress; 19 | import java.util.List; 20 | import java.util.concurrent.ArrayBlockingQueue; 21 | 22 | public class DatagramServer implements Runnable { 23 | public static final int MAX_QUEUE_SIZE = Global.getSettings().getInt("mpDatagramQueueLimit"); 24 | 25 | private final int port; 26 | private final ServerConnectionManager connectionManager; 27 | private final ArrayBlockingQueue messageQueue; 28 | 29 | private EventLoopGroup workerLoopGroup; 30 | private Channel channel; 31 | 32 | private final Clock clock; 33 | private boolean running; 34 | 35 | private final DebugGraphContainer dataGraph; 36 | private final DebugGraphContainer dataGraph2; 37 | // private final DebugGraphContainer dataGraphCompressed; 38 | // private final DebugGraphContainer dataGraphRatio; 39 | 40 | public DatagramServer(int port, ServerConnectionManager connectionManager) { 41 | this.port = port; 42 | this.connectionManager = connectionManager; 43 | 44 | messageQueue = new ArrayBlockingQueue<>(MAX_QUEUE_SIZE); 45 | 46 | clock = new Clock(ServerConnectionManager.TICK_RATE); 47 | 48 | dataGraph = new DebugGraphContainer("Packet Size", ServerConnectionManager.TICK_RATE * 4, 40f); 49 | dataGraph2 = new DebugGraphContainer("Packet Count", ServerConnectionManager.TICK_RATE * 4, 40f); 50 | // dataGraphCompressed = new DebugGraphContainer("Compressed Bytes Out", ServerConnectionManager.TICK_RATE, 50f); 51 | // dataGraphRatio = new DebugGraphContainer("Compression Ratio", ServerConnectionManager.TICK_RATE, 50f); 52 | 53 | running = false; 54 | } 55 | 56 | @Override 57 | public void run() { 58 | running = true; 59 | 60 | Console.showMessage("Running UDP server on port " + port + " at " + ServerConnectionManager.TICK_RATE + "Hz"); 61 | 62 | ChannelFuture channelFuture = start(); 63 | 64 | ChannelFuture closeFuture = channelFuture.channel().closeFuture(); 65 | 66 | try { 67 | int size = 0; 68 | int sizeCompressed = 0; 69 | int num = 0; 70 | 71 | while (connectionManager.isActive()) { 72 | while (!messageQueue.isEmpty()) { 73 | MessageContainer message = messageQueue.poll(); 74 | 75 | if (message == null || message.isEmpty()) continue; 76 | 77 | InetSocketAddress address = message.getDest(); 78 | DatagramUtils.SizeData sizeData = DatagramUtils.write(channel, message, message.getDest(), message.getConnectionID()); 79 | num++; 80 | 81 | if (sizeData != null) { 82 | size += sizeData.size; 83 | sizeCompressed += sizeData.sizeCompressed; 84 | } 85 | } 86 | 87 | if (clock.mark()) { 88 | dataGraph2.increment(num); 89 | dataGraph.increment(size); 90 | CMUtils.getGuiDebug().putContainer(DatagramServer.class, "dataGraph", dataGraph); 91 | CMUtils.getGuiDebug().putContainer(DatagramServer.class, "dataGraph2", dataGraph2); 92 | // dataGraphCompressed.increment(sizeCompressed); 93 | // CMUtils.getGuiDebug().putContainer(DatagramServer.class, "dataGraphCompressed", dataGraphCompressed); 94 | // dataGraphRatio.increment(100f * ((float) sizeCompressed / size)); 95 | // CMUtils.getGuiDebug().putContainer(DatagramServer.class, "dataGraphRatio", dataGraphRatio); 96 | 97 | size = 0; 98 | sizeCompressed = 0; 99 | num = 0; 100 | } 101 | } 102 | 103 | closeFuture.sync(); 104 | stop(); 105 | } catch (Throwable e) { 106 | e.printStackTrace(); 107 | } finally { 108 | System.err.println("CLOSING DATAGRAM THREAD"); 109 | } 110 | } 111 | 112 | private ChannelFuture start() { 113 | workerLoopGroup = new NioEventLoopGroup(); 114 | 115 | final Bootstrap bootstrap = new Bootstrap(); 116 | bootstrap.group(workerLoopGroup) 117 | .channel(NioDatagramChannel.class) 118 | .handler(new DatagramChannelInitializer(this, connectionManager) 119 | ); 120 | 121 | ChannelFuture future = bootstrap.bind(port).syncUninterruptibly(); 122 | channel = future.channel(); 123 | 124 | return future; 125 | } 126 | 127 | public void addMessages(List messages) { 128 | for (MessageContainer messageContainer : messages) { 129 | if (messageQueue.remainingCapacity() == 0) { 130 | messageQueue.poll(); 131 | } 132 | 133 | messageQueue.offer(messageContainer); 134 | } 135 | } 136 | 137 | public void stop() { 138 | if (channel != null) channel.close(); 139 | if (workerLoopGroup != null) workerLoopGroup.shutdownGracefully(); 140 | running = false; 141 | 142 | dataGraph.expire(); 143 | dataGraph2.expire(); 144 | // dataGraphCompressed.expire(); 145 | // dataGraphRatio.expire(); 146 | 147 | System.err.println("CLOSED DATAGRAM THREAD SYNC GRACEFULLY"); 148 | } 149 | 150 | public boolean isRunning() { 151 | return running; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/data/scripts/net/io/udp/server/ServerInboundHandler.java: -------------------------------------------------------------------------------- 1 | package data.scripts.net.io.udp.server; 2 | 3 | import data.scripts.net.data.InboundData; 4 | import data.scripts.net.io.ServerConnectionManager; 5 | import data.scripts.net.io.ServerConnectionWrapper; 6 | import data.scripts.net.io.Unpacked; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.SimpleChannelInboundHandler; 9 | 10 | public class ServerInboundHandler extends SimpleChannelInboundHandler { 11 | private final ServerConnectionManager serverConnectionManager; 12 | private ServerConnectionWrapper connectionWrapper; 13 | 14 | public ServerInboundHandler(ServerConnectionManager serverConnectionManager) { 15 | this.serverConnectionManager = serverConnectionManager; 16 | } 17 | 18 | @Override 19 | protected void channelRead0(ChannelHandlerContext ctx, Unpacked in) throws Exception { 20 | if (connectionWrapper == null) { 21 | connectionWrapper = serverConnectionManager.getConnection(in.getConnectionID()); 22 | } 23 | 24 | InboundData entities = in.getUnpacked(); 25 | entities.setSize(in.getSize()); 26 | 27 | connectionWrapper.updateInbound(entities); 28 | } 29 | 30 | @Override 31 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 32 | if (cause instanceof ArrayIndexOutOfBoundsException) { 33 | System.err.println("Malformed packet caught"); 34 | ctx.flush(); 35 | } else { 36 | System.err.println("Error caught in datagram channel: " + cause.getMessage()); 37 | cause.printStackTrace(); 38 | ctx.close(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/data/scripts/plugins/MPLogger.java: -------------------------------------------------------------------------------- 1 | package data.scripts.plugins; 2 | 3 | import com.fs.starfarer.api.Global; 4 | import data.scripts.plugins.gui.MPChatboxPlugin; 5 | 6 | public class MPLogger { 7 | 8 | public static void info(Class c, Object message) { 9 | Global.getLogger(c).info(message); 10 | 11 | if (message instanceof String) { 12 | String s = c.getSimpleName() + ": " + message; 13 | MPChatboxPlugin.INSTANCE.addEntry(new MPChatboxPlugin.ChatEntry(s, "[ SYS ]", (byte) 0xFF, true)); 14 | } 15 | } 16 | 17 | public static void error(Class c, Object message) { 18 | Global.getLogger(c).error(message); 19 | 20 | if (message instanceof String) { 21 | String s = c.getSimpleName() + ": " + message; 22 | MPChatboxPlugin.INSTANCE.addEntry(new MPChatboxPlugin.ChatEntry(s, "[ ERR ]", (byte) 0xFF, true)); 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/data/scripts/plugins/MPPlugin.java: -------------------------------------------------------------------------------- 1 | package data.scripts.plugins; 2 | 3 | import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin; 4 | import com.fs.starfarer.api.combat.CombatEngineAPI; 5 | import data.scripts.net.data.tables.BaseEntityManager; 6 | import data.scripts.net.data.datagen.BaseDatagen; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public abstract class MPPlugin extends BaseEveryFrameCombatPlugin { 12 | 13 | public static final String DATA_KEY = "mp_plugin"; 14 | 15 | public enum PluginType { 16 | SERVER, 17 | CLIENT 18 | } 19 | 20 | protected final Map, BaseEntityManager> entityManagers = new HashMap<>(); 21 | protected final Map, BaseDatagen> datastores = new HashMap<>(); 22 | 23 | public MPPlugin() { 24 | 25 | } 26 | 27 | @Override 28 | public void init(CombatEngineAPI engine) { 29 | engine.getCustomData().put(DATA_KEY, this); 30 | } 31 | 32 | public abstract void stop(); 33 | 34 | public abstract PluginType getType(); 35 | 36 | public void initEntityManager(BaseEntityManager manager) { 37 | entityManagers.put(manager.getClass(), manager); 38 | manager.register(); 39 | } 40 | 41 | public Map, BaseEntityManager> getEntityManagers() { 42 | return entityManagers; 43 | } 44 | 45 | public void removeEntityManager(Class clazz) { 46 | entityManagers.remove(clazz); 47 | } 48 | 49 | protected void updateEntityManagers(float amount) { 50 | for (BaseEntityManager manager : entityManagers.values()) manager.update(amount, this); 51 | } 52 | 53 | public void initDatastore(BaseDatagen datastore) { 54 | datastores.put(datastore.getClass(), datastore); 55 | datastore.generate(this); 56 | } 57 | 58 | public void genData() { 59 | for (BaseDatagen datastore : datastores.values()) datastore.generate(this); 60 | } 61 | 62 | public BaseDatagen getDatastore(Class clazz) { 63 | return datastores.get(clazz); 64 | } 65 | 66 | public void removeDatastore(Class clazz) { 67 | datastores.remove(clazz); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/data/scripts/plugins/MPServerPlugin.java: -------------------------------------------------------------------------------- 1 | package data.scripts.plugins; 2 | 3 | import cmu.CMUtils; 4 | import cmu.plugins.GUIDebug; 5 | import com.fs.starfarer.api.Global; 6 | import com.fs.starfarer.api.combat.CombatEngineAPI; 7 | import com.fs.starfarer.api.combat.CombatEntityAPI; 8 | import com.fs.starfarer.api.input.InputEventAPI; 9 | import data.scripts.net.data.DataGenManager; 10 | import data.scripts.net.data.InboundData; 11 | import data.scripts.net.data.OutboundData; 12 | import data.scripts.net.data.datagen.FighterVariantDatastore; 13 | import data.scripts.net.data.datagen.ProjectileSpecDatastore; 14 | import data.scripts.net.data.datagen.ShipVariantDatastore; 15 | import data.scripts.net.data.tables.OutboundEntityManager; 16 | import data.scripts.net.data.tables.server.combat.connection.TextChatHost; 17 | import data.scripts.net.data.tables.server.combat.entities.ProjectileTable; 18 | import data.scripts.net.data.tables.server.combat.entities.ships.ShipTable; 19 | import data.scripts.net.data.tables.server.combat.players.PlayerLobby; 20 | import data.scripts.net.data.tables.server.combat.players.PlayerShips; 21 | import data.scripts.net.io.ServerConnectionManager; 22 | import data.scripts.plugins.gui.MPChatboxPlugin; 23 | import org.lazywizard.console.Console; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import java.util.Map; 28 | 29 | public class MPServerPlugin extends MPPlugin { 30 | 31 | //inbound 32 | private final ServerConnectionManager serverConnectionManager; 33 | private final PlayerLobby playerLobby; 34 | private final PlayerShips playerShips; 35 | 36 | //outbound 37 | private final ShipTable shipTable; 38 | private final ProjectileTable projectileTable; 39 | 40 | private final ShipVariantDatastore variantDatastore; 41 | private final ProjectileSpecDatastore projectileSpecDatastore; 42 | private final FighterVariantDatastore fighterVariantDatastore; 43 | 44 | private final TextChatHost textChatHost; 45 | 46 | public MPServerPlugin(int port) { 47 | CombatEngineAPI engine = Global.getCombatEngine(); 48 | 49 | MPChatboxPlugin chatboxPlugin = new MPChatboxPlugin(); 50 | engine.addPlugin(chatboxPlugin); 51 | 52 | variantDatastore = new ShipVariantDatastore(); 53 | initDatastore(variantDatastore); 54 | projectileSpecDatastore = new ProjectileSpecDatastore(); 55 | initDatastore(projectileSpecDatastore); 56 | fighterVariantDatastore = new FighterVariantDatastore(); 57 | initDatastore(fighterVariantDatastore); 58 | 59 | serverConnectionManager = new ServerConnectionManager(this, port); 60 | 61 | shipTable = new ShipTable(this); 62 | initEntityManager(shipTable); 63 | 64 | playerShips = new PlayerShips(shipTable); 65 | initEntityManager(playerShips); 66 | 67 | playerLobby = new PlayerLobby(this, playerShips); 68 | initEntityManager(playerLobby); 69 | 70 | projectileTable = new ProjectileTable(projectileSpecDatastore, shipTable); 71 | initEntityManager(projectileTable); 72 | 73 | textChatHost = new TextChatHost(chatboxPlugin, playerLobby); 74 | initEntityManager(textChatHost); 75 | 76 | Thread serverThread = new Thread(serverConnectionManager, "MP_SERVER_THREAD"); 77 | serverThread.start(); 78 | 79 | MPLogger.info(MPServerPlugin.class, "INITIALISATION COMPLETE"); 80 | MPLogger.info(MPServerPlugin.class, "WELCOME STARFARER"); 81 | } 82 | 83 | @Override 84 | public void advance(float amount, List events) { 85 | // inbound data update 86 | Map inbound = serverConnectionManager.getDuplex().getDeltas(); 87 | for (byte connectionID : inbound.keySet()) { 88 | InboundData data = inbound.get(connectionID); 89 | DataGenManager.distributeInboundDeltas(data, this, serverConnectionManager.getTick(), connectionID); 90 | } 91 | 92 | CombatEngineAPI engine = Global.getCombatEngine(); 93 | 94 | // sorry 95 | List asteroids = new ArrayList<>(engine.getAsteroids()); 96 | for (CombatEntityAPI asteroid : asteroids) { 97 | engine.removeEntity(asteroid); 98 | } 99 | 100 | // simulation update 101 | updateEntityManagers(amount); 102 | 103 | // outbound data update 104 | List connectionIDs = new ArrayList<>(serverConnectionManager.getServerConnectionWrappers().keySet()); 105 | 106 | Map outboundSocket = DataGenManager.collectOutboundDeltas(amount, connectionIDs, OutboundEntityManager.PacketType.SOCKET); 107 | Map outboundDatagram = DataGenManager.collectOutboundDeltas(amount, connectionIDs, OutboundEntityManager.PacketType.DATAGRAM); 108 | 109 | for (byte connectionID : connectionIDs) { 110 | serverConnectionManager.getDuplex().updateOutboundSocket(connectionID, outboundSocket.get(connectionID)); 111 | serverConnectionManager.getDuplex().updateOutboundDatagram(connectionID, outboundDatagram.get(connectionID)); 112 | } 113 | 114 | debug(); 115 | } 116 | 117 | @Override 118 | public void stop() { 119 | serverConnectionManager.stop(); 120 | Global.getCombatEngine().removePlugin(this); 121 | Console.showMessage("Closed server"); 122 | } 123 | 124 | private void debug() { 125 | GUIDebug guiDebug = CMUtils.getGuiDebug(); 126 | 127 | guiDebug.putText(MPServerPlugin.class, "clients", serverConnectionManager.getServerConnectionWrappers().size() + " remote clients connected"); 128 | guiDebug.putText(MPServerPlugin.class, "shipCount", "tracking " + shipTable.getRegistered().size() + " ships in local table"); 129 | guiDebug.putText(MPServerPlugin.class, "tick", "current server tick " + serverConnectionManager.getTick() + " @ " + ServerConnectionManager.TICK_RATE + "Hz"); 130 | } 131 | 132 | public ShipVariantDatastore getVariantStore() { 133 | return variantDatastore; 134 | } 135 | 136 | public FighterVariantDatastore getFighterVariantDatastore() { 137 | return fighterVariantDatastore; 138 | } 139 | 140 | @Override 141 | public PluginType getType() { 142 | return PluginType.SERVER; 143 | } 144 | } -------------------------------------------------------------------------------- /src/data/scripts/plugins/ai/MPDefaultAutofireAIPlugin.java: -------------------------------------------------------------------------------- 1 | package data.scripts.plugins.ai; 2 | 3 | import com.fs.starfarer.api.combat.AutofireAIPlugin; 4 | import com.fs.starfarer.api.combat.MissileAPI; 5 | import com.fs.starfarer.api.combat.ShipAPI; 6 | import com.fs.starfarer.api.combat.WeaponAPI; 7 | import org.lwjgl.util.vector.Vector2f; 8 | 9 | public class MPDefaultAutofireAIPlugin implements AutofireAIPlugin { 10 | 11 | private final WeaponAPI weapon; 12 | 13 | public MPDefaultAutofireAIPlugin(WeaponAPI weapon) { 14 | this.weapon = weapon; 15 | } 16 | 17 | @Override 18 | public void advance(float amount) { 19 | 20 | } 21 | 22 | @Override 23 | public boolean shouldFire() { 24 | return false; 25 | } 26 | 27 | @Override 28 | public void forceOff() { 29 | 30 | } 31 | 32 | @Override 33 | public Vector2f getTarget() { 34 | return null; 35 | } 36 | 37 | @Override 38 | public ShipAPI getTargetShip() { 39 | return null; 40 | } 41 | 42 | @Override 43 | public WeaponAPI getWeapon() { 44 | return weapon; 45 | } 46 | 47 | @Override 48 | public MissileAPI getTargetMissile() { 49 | return null; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/data/scripts/plugins/ai/MPDefaultMissileAIPlugin.java: -------------------------------------------------------------------------------- 1 | package data.scripts.plugins.ai; 2 | 3 | import com.fs.starfarer.api.combat.MissileAIPlugin; 4 | 5 | public class MPDefaultMissileAIPlugin implements MissileAIPlugin { 6 | @Override 7 | public void advance(float amount) { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/data/scripts/plugins/ai/MPDefaultShipAIPlugin.java: -------------------------------------------------------------------------------- 1 | package data.scripts.plugins.ai; 2 | 3 | import com.fs.starfarer.api.combat.ShipAIConfig; 4 | import com.fs.starfarer.api.combat.ShipAIPlugin; 5 | import com.fs.starfarer.api.combat.ShipwideAIFlags; 6 | 7 | public class MPDefaultShipAIPlugin implements ShipAIPlugin { 8 | @Override 9 | public void setDoNotFireDelay(float amount) { 10 | 11 | } 12 | 13 | @Override 14 | public void forceCircumstanceEvaluation() { 15 | 16 | } 17 | 18 | @Override 19 | public void advance(float amount) { 20 | 21 | } 22 | 23 | @Override 24 | public boolean needsRefit() { 25 | return false; 26 | } 27 | 28 | @Override 29 | public ShipwideAIFlags getAIFlags() { 30 | return new ShipwideAIFlags(); 31 | } 32 | 33 | @Override 34 | public void cancelCurrentManeuver() { 35 | 36 | } 37 | 38 | @Override 39 | public ShipAIConfig getConfig() { 40 | return new ShipAIConfig(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/data/scripts/test/MPTest.java: -------------------------------------------------------------------------------- 1 | package data.scripts.test; 2 | 3 | public class MPTest { 4 | 5 | public MPTest() { 6 | 7 | } 8 | 9 | public void run() { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /target/classes/data/scripts/console/commands/mpRunServer.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/automatopaste/Multiplayer/6ea94d27b83589eb86c56b5fd51910776b51f6c3/target/classes/data/scripts/console/commands/mpRunServer.class --------------------------------------------------------------------------------