diff --git a/README.md b/README.md index 6d80675ca..fa82e05a5 100644 --- a/README.md +++ b/README.md @@ -60,15 +60,29 @@ It is recommended to use IntelliJ IDEA to develop with Void. The community edition can be downloaded for free from the [jetbrains website.](https://www.jetbrains.com/idea/download/) See [the installation guide](https://www.jetbrains.com/help/idea/installation-guide.html) for more instructions. -Once inside the IDE, you can create a new project by going to `File | New | Project from version control... |` +Once opened the IDE click the `Clone Repository` button or `File | New | Project from version control... |` if in the full application. -Selecting `git` version control and entering the void project URL `git@github.com:GregHib/void.git` found under the `<> Code` button on the [GitHub page](https://github.com/GregHib/void). +Selecting `git` version control and entering the void project URL Found under the `<> Code` button on the [GitHub page](https://github.com/GregHib/void). +- `git@github.com:GregHib/void.git` if you have [GitHub authentication setup](https://docs.github.com/en/authentication). +- `https://github.com/GregHib/void.git` if you don't have SSH authentication. -Press clone and after a little while the project will be opened for you, the JDK indexed and gradle setup. +> [!NOTE] +> When git is not installed it will display an error and the option to "Download and install", click this and retry the previous step. +> Click "Trust Project" if also asked. + +Press "clone" and after the download is complete the project will be opened for you. + +Under `Project Structure... | Project` settings set `SDK` to JDk 19+ (download as needed) and let it index. + +Run the following command in the terminal to set up Gradle or close and re-open the project to trigger IntelliJ's `Open as Gradle Project` popup. + +```bash +./gradlew build -x test +``` Extract the [cache files](https://mega.nz/folder/ZMN2AQaZ#4rJgfzbVW0_mWsr1oPLh1A) into a new directory called `/cache/` inside of the `/data/` directory. -From here you can navigate in the left panel to `/game/src/main/kotlin/world/gregs/voidps/` where you will find [Main.kt](./game/src/main/kotlin/world/gregs/voidps/Main.kt) which you should be able to right-click and run. +From here you can navigate in the left panel to `/game/src/main/kotlin/world/gregs/voidps/` (Or Ctrl/Cmd + N for class search) where you will find [Main.kt](./game/src/main/kotlin/world/gregs/voidps/Main.kt) which you should be able to right-click and run. You can also run in the command line using the gradle wrapper. @@ -76,7 +90,7 @@ You can also run in the command line using the gradle wrapper. ./gradlew run ``` -Once the server is up and running; setup the [void-client repository](https://github.com/GregHib/void-client/) or download one of the [prebuilt client.jars](https://github.com/GregHib/void-client/releases) and run to log into the game. +Once the server is up and running; download one of the [prebuilt client.jars](https://github.com/GregHib/void-client/releases) or set up the [void-client repository](https://github.com/GregHib/void-client/) and run to log into the game. Don't forget to check out our [Contributing guidelines](./CONTRIBUTING.md) before submitting your first pull request! diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt index 20fcd72c2..d9a6b4ab7 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/client/PlayerAccountLoader.kt @@ -52,7 +52,7 @@ class PlayerAccountLoader( client.disconnect(Response.ACCOUNT_ONLINE) return null } - val player = storage.load(username)?.toPlayer(Settings["world.experienceRate", 1.0]) ?: accounts.create(username, passwordHash) + val player = storage.load(username)?.toPlayer() ?: accounts.create(username, passwordHash) logger.info { "Player $username loaded and queued for login." } connect(player, client, displayMode) return player.instructions diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/AccountManager.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/AccountManager.kt index 29801e376..66b3cafef 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/AccountManager.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/AccountManager.kt @@ -45,7 +45,7 @@ class AccountManager( get() = Tile(Settings["world.home.x", 0], Settings["world.home.y", 0], Settings["world.home.level", 0]) fun create(name: String, passwordHash: String): Player { - return Player(tile = homeTile, accountName = name, passwordHash = passwordHash, experience = Experience(rate = Settings["world.experienceRate", 1.0])).apply { + return Player(tile = homeTile, accountName = name, passwordHash = passwordHash).apply { this["creation"] = System.currentTimeMillis() this["new_player"] = true } diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/PlayerSave.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/PlayerSave.kt index db0469333..07d8c4325 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/PlayerSave.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/PlayerSave.kt @@ -26,12 +26,12 @@ data class PlayerSave( val ignores: List ) { - fun toPlayer(rate: Double): Player { + fun toPlayer(): Player { return Player( accountName = name, passwordHash = password, tile = tile, - experience = Experience(experience, blocked.toMutableSet(), rate = rate), + experience = Experience(experience, blocked.toMutableSet()), levels = Levels(levels), body = BodyParts(male, looks, colours), variables = variables.toMutableMap(), diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/data/yaml/PlayerYamlReaderConfig.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/data/yaml/PlayerYamlReaderConfig.kt index 998f77f25..c1f1015c3 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/data/yaml/PlayerYamlReaderConfig.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/data/yaml/PlayerYamlReaderConfig.kt @@ -34,7 +34,6 @@ internal class PlayerYamlReaderConfig : YamlReaderConfiguration() { val exp = Experience( experience = (value["experience"] as List).toDoubleArray(), blocked = (value["blocked"] as List).toMutableSet(), - rate = Settings["world.experienceRate", 1.0] ) super.set(map, key, exp, indent, parentMap) } else if (key == "levels") { diff --git a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/player/skill/exp/Experience.kt b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/player/skill/exp/Experience.kt index 407a27213..e3983fd07 100644 --- a/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/player/skill/exp/Experience.kt +++ b/engine/src/main/kotlin/world/gregs/voidps/engine/entity/character/player/skill/exp/Experience.kt @@ -1,5 +1,6 @@ package world.gregs.voidps.engine.entity.character.player.skill.exp +import world.gregs.voidps.engine.data.Settings import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.skill.Skill import world.gregs.voidps.engine.entity.character.player.skill.level.Level @@ -8,8 +9,7 @@ import world.gregs.voidps.engine.event.EventDispatcher class Experience( val experience: DoubleArray = defaultExperience.clone(), val blocked: MutableSet = mutableSetOf(), - private val maximum: Double = MAXIMUM_EXPERIENCE, - private val rate: Double = DEFAULT_EXPERIENCE_RATE + private val maximum: Double = MAXIMUM_EXPERIENCE ) { lateinit var events: EventDispatcher @@ -36,10 +36,10 @@ class Experience( return } if (blocked.contains(skill)) { - events.emit(BlockedExperience(skill, experience * rate)) + events.emit(BlockedExperience(skill, experience * Settings["world.experienceRate", DEFAULT_EXPERIENCE_RATE])) } else { val current = get(skill) - set(skill, current + experience * rate) + set(skill, current + experience * Settings["world.experienceRate", DEFAULT_EXPERIENCE_RATE]) } } diff --git a/engine/src/test/kotlin/world/gregs/voidps/engine/entity/character/player/skill/ExperienceTableTest.kt b/engine/src/test/kotlin/world/gregs/voidps/engine/entity/character/player/skill/ExperienceTableTest.kt index 8957119e4..f9a1f0a41 100644 --- a/engine/src/test/kotlin/world/gregs/voidps/engine/entity/character/player/skill/ExperienceTableTest.kt +++ b/engine/src/test/kotlin/world/gregs/voidps/engine/entity/character/player/skill/ExperienceTableTest.kt @@ -7,9 +7,11 @@ import io.mockk.verify import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import world.gregs.voidps.engine.data.Settings import world.gregs.voidps.engine.entity.character.player.Player import world.gregs.voidps.engine.entity.character.player.skill.exp.BlockedExperience import world.gregs.voidps.engine.entity.character.player.skill.exp.Experience +import world.gregs.voidps.engine.entity.character.player.skill.exp.Experience.Companion.DEFAULT_EXPERIENCE_RATE import world.gregs.voidps.engine.entity.character.player.skill.exp.GrantExp import world.gregs.voidps.engine.entity.character.player.skill.exp.exp import world.gregs.voidps.engine.event.EventDispatcher @@ -48,11 +50,13 @@ internal class ExperienceTableTest { @Test fun `Add experience with 10x rate`() { - experience = Experience(maximum = 500.0, rate = 10.0) + Settings.load(mapOf("world.experienceRate" to "10.0")) + experience = Experience(maximum = 500.0) experience.events = events experience.add(Skill.Attack, 10.0) experience.add(Skill.Attack, 10.0) assertEquals(200.0, experience.get(Skill.Attack)) + Settings.clear() } @Test diff --git a/game/src/main/kotlin/world/gregs/voidps/Main.kt b/game/src/main/kotlin/world/gregs/voidps/Main.kt index cea626b9c..d898c245e 100644 --- a/game/src/main/kotlin/world/gregs/voidps/Main.kt +++ b/game/src/main/kotlin/world/gregs/voidps/Main.kt @@ -24,15 +24,13 @@ import world.gregs.voidps.network.LoginServer import world.gregs.voidps.network.login.protocol.decoders import world.gregs.voidps.script.loadScripts import java.util.* -import kotlin.coroutines.CoroutineContext /** * @author GregHib * @since April 18, 2020 */ -object Main : CoroutineScope { +object Main { - override val coroutineContext: CoroutineContext = Contexts.Game private val logger = InlineLogger() @OptIn(ExperimentalUnsignedTypes::class) diff --git a/game/src/main/kotlin/world/gregs/voidps/world/command/debug/DebugCommands.kts b/game/src/main/kotlin/world/gregs/voidps/world/command/debug/DebugCommands.kts index 93e03f383..6d1362aac 100644 --- a/game/src/main/kotlin/world/gregs/voidps/world/command/debug/DebugCommands.kts +++ b/game/src/main/kotlin/world/gregs/voidps/world/command/debug/DebugCommands.kts @@ -274,22 +274,12 @@ adminCommand("sendItems") { player.sendInventoryItems(90, 28, ags, true) } -adminCommand("obj (object-id) [object-type] [object-rotation]", "spawn an object") { - if (content.isNotBlank()) { - val parts = content.split(" ") - val id = parts.getOrNull(0) - if (id != null) { - val rotation = parts.getOrNull(1)?.toIntOrNull() ?: 0 - objects.add(id, player.tile.addY(1), 0, rotation, 10) - objects.add(id, player.tile.addY(1), 10, rotation, 10) - objects.add(id, player.tile.addY(1), 22, rotation, 10) - } - } else { - val objs = get() - objs[player.tile].forEach { - println(it.intId) - } - } +adminCommand("obj (object-id) [object-shape] [object-rotation]", "spawn an object") { + val parts = content.split(" ") + val id = parts[0] + val shape = parts.getOrNull(1)?.toIntOrNull() ?: 10 + val rotation = parts.getOrNull(2)?.toIntOrNull() ?: 0 + objects.add(id, player.tile, shape, rotation) } diff --git a/game/src/main/kotlin/world/gregs/voidps/world/interact/entity/npc/combat/Aggression.kts b/game/src/main/kotlin/world/gregs/voidps/world/interact/entity/npc/combat/Aggression.kts index 556f38c2d..c1e40b95d 100644 --- a/game/src/main/kotlin/world/gregs/voidps/world/interact/entity/npc/combat/Aggression.kts +++ b/game/src/main/kotlin/world/gregs/voidps/world/interact/entity/npc/combat/Aggression.kts @@ -11,7 +11,7 @@ import world.gregs.voidps.engine.entity.character.npc.hunt.huntPlayer import world.gregs.voidps.engine.entity.character.player.PlayerOption huntPlayer(mode = "aggressive") { npc -> - if (attacking(npc, target)) { + if (!Settings["world.npcs.aggression", true] || attacking(npc, target)) { return@huntPlayer } if (Settings["world.npcs.safeZone", false] && npc.tile.region.id == 12850) { @@ -21,7 +21,7 @@ huntPlayer(mode = "aggressive") { npc -> } huntPlayer(mode = "cowardly") { npc -> - if (attacking(npc, target)) { + if (!Settings["world.npcs.aggression", true] || attacking(npc, target)) { return@huntPlayer } npc.mode = Interact(npc, target, PlayerOption(npc, target, "Attack")) diff --git a/game/src/main/kotlin/world/gregs/voidps/world/interact/entity/player/AutoSave.kts b/game/src/main/kotlin/world/gregs/voidps/world/interact/entity/player/AutoSave.kts new file mode 100644 index 000000000..dee48741e --- /dev/null +++ b/game/src/main/kotlin/world/gregs/voidps/world/interact/entity/player/AutoSave.kts @@ -0,0 +1,40 @@ +package world.gregs.voidps.world.interact.entity.player + +import world.gregs.voidps.engine.data.SaveQueue +import world.gregs.voidps.engine.data.Settings +import world.gregs.voidps.engine.data.settingsReload +import world.gregs.voidps.engine.entity.World +import world.gregs.voidps.engine.entity.character.player.Players +import world.gregs.voidps.engine.entity.worldSpawn +import world.gregs.voidps.engine.inject +import world.gregs.voidps.engine.timer.toTicks +import java.util.concurrent.TimeUnit + +val players: Players by inject() +val saveQueue: SaveQueue by inject() + +worldSpawn { + autoSave() +} + +settingsReload { + val minutes = Settings["storage.autoSave.minutes", 0] + if (World.contains("auto_save") && minutes <= 0) { + World.clearQueue("auto_save") + } else if (!World.contains("auto_save") && minutes > 0) { + autoSave() + } +} + +fun autoSave() { + val minutes = Settings["storage.autoSave.minutes", 0] + if (minutes <= 0) { + return + } + World.queue("auto_save", TimeUnit.MINUTES.toTicks(minutes)) { + for (player in players) { + saveQueue.save(player) + } + autoSave() + } +} \ No newline at end of file diff --git a/game/src/main/resources/game.properties b/game/src/main/resources/game.properties index 42e149f6a..809a4c761 100644 --- a/game/src/main/resources/game.properties +++ b/game/src/main/resources/game.properties @@ -59,11 +59,14 @@ world.home.y=3219 #world.home.level=0 # Whether content should broadcast global messages -world.messages=true +world.messages=false # Experience multiplier (1.0 = normal XP rate) world.experienceRate=1.0 +# Experience multiplier (1.0 = normal XP rate) +world.saveEveryMinutes=1 + #------- Player Rules ------- # The maximum number of players that can be online at once @@ -89,6 +92,9 @@ world.npcs.randomWalk=true # Whether Lumbridge is an NPC aggression-free area world.npcs.safeZone=true +# Whether NPCs can attack players +world.npcs.aggression=true + #=================================== # Gameplay Mechanics @@ -140,7 +146,7 @@ events.shootingStars.maxRespawnTimeMinutes=120 #=================================== # The number of AI-controlled bots spawned on startup -bots.count=10 +bots.count=0 # What tasks to give AI-controlled bots with no tasks (options: nothing, randomWalk) bots.idle=randomWalk @@ -153,6 +159,9 @@ bots.idle=randomWalk # The type of storage backend (options: files, database) storage.type=files +# How frequently to save players files (0 to only save on logout) +storage.autoSave.minutes=5 + # The directory where player save files are stored storage.players.path=./data/saves/