mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-12 08:49:22 +07:00
Big changes to mods and rulesets - almost production ready!
Rulesets are heavy to load so we now have a RulesetCache, which can construct "custom" rulesets with a list of mods! We now pack mod images on Desktop run, and load the atlases for the loaded mods on load game!
This commit is contained in:
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
13
android/assets/modss/myFirstMod/game.atlas
Normal file
13
android/assets/modss/myFirstMod/game.atlas
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
game.png
|
||||
size: 128,128
|
||||
format: RGBA8888
|
||||
filter: MipMapLinearLinear,MipMapLinearLinear
|
||||
repeat: none
|
||||
UnitIcons/Maori Warrior
|
||||
rotate: false
|
||||
xy: 2, 2
|
||||
size: 100, 100
|
||||
orig: 100, 100
|
||||
offset: 0, 0
|
||||
index: -1
|
BIN
android/assets/modss/myFirstMod/game.png
Normal file
BIN
android/assets/modss/myFirstMod/game.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
@ -13,7 +13,7 @@ import com.unciv.logic.GameStarter
|
||||
import com.unciv.logic.map.MapParameters
|
||||
import com.unciv.models.metadata.GameParameters
|
||||
import com.unciv.models.metadata.GameSettings
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.translations.TranslationFileReader
|
||||
import com.unciv.models.translations.Translations
|
||||
import com.unciv.ui.LanguagePickerScreen
|
||||
@ -37,7 +37,7 @@ class UncivGame(
|
||||
/** For when you need to test something in an advanced game and don't have time to faff around */
|
||||
val superchargedForDebug = false
|
||||
|
||||
var rewriteTranslationFiles = false
|
||||
var rewriteTranslationFiles = true
|
||||
|
||||
|
||||
lateinit var worldScreen: WorldScreen
|
||||
@ -46,7 +46,6 @@ class UncivGame(
|
||||
val musicLocation = "music/thatched-villagers.mp3"
|
||||
var isInitialized = false
|
||||
|
||||
lateinit var ruleset:Ruleset
|
||||
|
||||
val translations = Translations()
|
||||
|
||||
@ -68,7 +67,7 @@ class UncivGame(
|
||||
screen = LoadingScreen()
|
||||
|
||||
thread(name="LoadJSON") {
|
||||
ruleset = Ruleset(true)
|
||||
RulesetCache.loadRulesets()
|
||||
|
||||
if (rewriteTranslationFiles) { // Yes, also when running from the Jar. Sue me.
|
||||
translations.readAllLanguagesTranslation()
|
||||
@ -122,7 +121,8 @@ class UncivGame(
|
||||
|
||||
fun loadGame(gameInfo:GameInfo){
|
||||
this.gameInfo = gameInfo
|
||||
|
||||
ImageGetter.ruleset = gameInfo.ruleSet
|
||||
ImageGetter.refreshAltas()
|
||||
worldScreen = WorldScreen(gameInfo.getPlayerToViewAs())
|
||||
setWorldScreen()
|
||||
}
|
||||
|
@ -10,8 +10,10 @@ import com.unciv.logic.civilization.LocationAction
|
||||
import com.unciv.logic.civilization.PlayerType
|
||||
import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.logic.map.TileMap
|
||||
import com.unciv.models.ruleset.Difficulty
|
||||
import com.unciv.models.metadata.GameParameters
|
||||
import com.unciv.models.ruleset.Difficulty
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import java.util.*
|
||||
|
||||
class GameInfo {
|
||||
@ -20,7 +22,7 @@ class GameInfo {
|
||||
/** This is used in multiplayer games, where I may have a saved game state on my phone
|
||||
* that is inconsistent with the saved game on the cloud */
|
||||
@Transient var isUpToDate=false
|
||||
@Transient var ruleSet = UncivGame.Current.ruleset
|
||||
@Transient lateinit var ruleSet:Ruleset
|
||||
|
||||
var civilizations = mutableListOf<CivilizationInfo>()
|
||||
var difficulty="Chieftain" // difficulty is game-wide, think what would happen if 2 human players could play on different difficulties?
|
||||
@ -203,7 +205,7 @@ class GameInfo {
|
||||
// will be done here, and not in CivInfo.setTransients or CityInfo
|
||||
fun setTransients() {
|
||||
tileMap.gameInfo = this
|
||||
|
||||
ruleSet = RulesetCache.getComplexRuleset(gameParameters.mods)
|
||||
|
||||
// Renames as of version 3.1.8, because of translation conflicts with the property "Range" and the difficulty "Immortal"
|
||||
// Needs to be BEFORE tileMap.setTransients, because the units' setTransients is called from there
|
||||
|
@ -2,11 +2,11 @@ package com.unciv.logic
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.logic.map.*
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.metadata.GameParameters
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.max
|
||||
@ -17,20 +17,20 @@ class GameStarter{
|
||||
val gameInfo = GameInfo()
|
||||
|
||||
gameInfo.gameParameters = newGameParameters
|
||||
val gameBasics = UncivGame.Current.ruleset
|
||||
val ruleset = RulesetCache.getComplexRuleset(newGameParameters.mods)
|
||||
|
||||
if(mapParameters.name!="")
|
||||
gameInfo.tileMap = MapSaver().loadMap(mapParameters.name)
|
||||
else gameInfo.tileMap = MapGenerator().generateMap(mapParameters, gameBasics)
|
||||
else gameInfo.tileMap = MapGenerator().generateMap(mapParameters, ruleset)
|
||||
gameInfo.tileMap.mapParameters = mapParameters
|
||||
|
||||
gameInfo.tileMap.setTransients(gameBasics)
|
||||
gameInfo.tileMap.setTransients(ruleset)
|
||||
|
||||
gameInfo.tileMap.gameInfo = gameInfo // need to set this transient before placing units in the map
|
||||
gameInfo.difficulty = newGameParameters.difficulty
|
||||
|
||||
|
||||
addCivilizations(newGameParameters, gameInfo, gameBasics) // this is before the setTransients so gameInfo doesn't yet have the gameBasics
|
||||
addCivilizations(newGameParameters, gameInfo, ruleset) // this is before the setTransients so gameInfo doesn't yet have the gameBasics
|
||||
|
||||
gameInfo.setTransients() // needs to be before placeBarbarianUnit because it depends on the tilemap having its gameinfo set
|
||||
|
||||
@ -41,7 +41,7 @@ class GameStarter{
|
||||
for (tech in gameInfo.getDifficulty().aiFreeTechs)
|
||||
civInfo.tech.addTechnology(tech)
|
||||
|
||||
for (tech in gameBasics.technologies.values
|
||||
for (tech in ruleset.technologies.values
|
||||
.filter { it.era() < newGameParameters.startingEra })
|
||||
if (!civInfo.tech.isResearched(tech.name))
|
||||
civInfo.tech.addTechnology(tech.name)
|
||||
|
@ -20,4 +20,5 @@ class GameParameters { // Default values are the default new game
|
||||
var startingEra = TechEra.Ancient
|
||||
|
||||
var isOnlineMultiplayer = false
|
||||
var mods = HashSet<String>()
|
||||
}
|
@ -13,12 +13,11 @@ import com.unciv.models.ruleset.unit.Promotion
|
||||
import com.unciv.models.stats.INamed
|
||||
import kotlin.collections.set
|
||||
|
||||
class Ruleset(load: Boolean = true) {
|
||||
class Ruleset() {
|
||||
|
||||
private val jsonParser = JsonParser()
|
||||
|
||||
var name = ""
|
||||
val mods = LinkedHashSet<String>()
|
||||
val buildings = LinkedHashMap<String, Building>()
|
||||
val terrains = LinkedHashMap<String, Terrain>()
|
||||
val tileResources = LinkedHashMap<String, TileResource>()
|
||||
@ -29,19 +28,14 @@ class Ruleset(load: Boolean = true) {
|
||||
val nations = LinkedHashMap<String, Nation>()
|
||||
val policyBranches = LinkedHashMap<String, PolicyBranch>()
|
||||
val difficulties = LinkedHashMap<String, Difficulty>()
|
||||
val mods = HashSet<String>()
|
||||
|
||||
fun clone(): Ruleset {
|
||||
val newRuleset = Ruleset(false)
|
||||
val newRuleset = Ruleset()
|
||||
newRuleset.add(this)
|
||||
return newRuleset
|
||||
}
|
||||
|
||||
init {
|
||||
if (load) {
|
||||
load(Gdx.files.internal("jsons"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : INamed> createHashmap(items: Array<T>): LinkedHashMap<String, T> {
|
||||
val hashMap = LinkedHashMap<String, T>()
|
||||
for (item in items)
|
||||
@ -63,7 +57,7 @@ class Ruleset(load: Boolean = true) {
|
||||
units.putAll(ruleset.units)
|
||||
}
|
||||
|
||||
fun clearExceptModNames() {
|
||||
fun clear() {
|
||||
buildings.clear()
|
||||
difficulties.clear()
|
||||
nations.clear()
|
||||
@ -75,6 +69,7 @@ class Ruleset(load: Boolean = true) {
|
||||
tileResources.clear()
|
||||
unitPromotions.clear()
|
||||
units.clear()
|
||||
mods.clear()
|
||||
}
|
||||
|
||||
fun load(folderHandle :FileHandle ) {
|
||||
@ -146,3 +141,34 @@ class Ruleset(load: Boolean = true) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Loading mods is expensive, so let's only do it once and
|
||||
* save all of the loaded rulesets somewhere for later use
|
||||
* */
|
||||
object RulesetCache :HashMap<String,Ruleset>(){
|
||||
fun loadRulesets(){
|
||||
this[""] = Ruleset().apply { load(Gdx.files.internal("jsons")) }
|
||||
|
||||
for(modFolder in Gdx.files.local("mods").list()){
|
||||
try{
|
||||
val modRuleset = Ruleset()
|
||||
modRuleset.load(modFolder.child("jsons"))
|
||||
modRuleset.name = modFolder.name()
|
||||
this[modRuleset.name] = modRuleset
|
||||
}
|
||||
catch (ex:Exception){}
|
||||
}
|
||||
}
|
||||
|
||||
fun getBaseRuleset() = this[""]!!
|
||||
|
||||
fun getComplexRuleset(mods:Collection<String>): Ruleset {
|
||||
val newRuleset = Ruleset()
|
||||
newRuleset.add(getBaseRuleset())
|
||||
for(mod in mods)
|
||||
if(containsKey(mod)) {
|
||||
newRuleset.add(this[mod]!!)
|
||||
newRuleset.mods+=mod
|
||||
}
|
||||
return newRuleset
|
||||
}
|
||||
}
|
@ -6,16 +6,16 @@ import com.badlogic.gdx.scenes.scene2d.InputEvent
|
||||
import com.badlogic.gdx.scenes.scene2d.InputListener
|
||||
import com.badlogic.gdx.scenes.scene2d.actions.Actions
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.MapSaver
|
||||
import com.unciv.logic.map.TileMap
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.utils.CameraStageBaseScreen
|
||||
import com.unciv.ui.utils.onClick
|
||||
import com.unciv.ui.utils.setFontSize
|
||||
|
||||
class MapEditorScreen(): CameraStageBaseScreen() {
|
||||
val ruleset = UncivGame.Current.ruleset
|
||||
val ruleset = RulesetCache.getBaseRuleset()
|
||||
var mapName = "My first map"
|
||||
|
||||
var tileMap = TileMap()
|
||||
|
@ -10,12 +10,12 @@ import com.unciv.logic.GameInfo
|
||||
import com.unciv.logic.GameSaver
|
||||
import com.unciv.logic.GameStarter
|
||||
import com.unciv.logic.civilization.PlayerType
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.pickerscreens.PickerScreen
|
||||
import com.unciv.ui.utils.disable
|
||||
import com.unciv.ui.utils.enable
|
||||
import com.unciv.ui.utils.onClick
|
||||
import com.unciv.ui.worldscreen.WorldScreen
|
||||
import com.unciv.ui.worldscreen.optionstable.OnlineMultiplayer
|
||||
import com.unciv.ui.worldscreen.optionstable.PopupTable
|
||||
import java.util.*
|
||||
@ -25,7 +25,7 @@ class NewGameScreen: PickerScreen(){
|
||||
|
||||
val newGameParameters= UncivGame.Current.gameInfo.gameParameters
|
||||
val mapParameters = UncivGame.Current.gameInfo.tileMap.mapParameters
|
||||
val ruleSet = UncivGame.Current.ruleset.clone()
|
||||
val ruleSet = RulesetCache.getComplexRuleset(newGameParameters.mods)
|
||||
|
||||
init {
|
||||
setDefaultCloseAction()
|
||||
@ -105,9 +105,7 @@ class NewGameScreen: PickerScreen(){
|
||||
|
||||
override fun render(delta: Float) {
|
||||
if(newGame!=null){
|
||||
game.gameInfo=newGame!!
|
||||
game.worldScreen = WorldScreen(newGame!!.currentPlayerCiv)
|
||||
game.setWorldScreen()
|
||||
game.loadGame(newGame!!)
|
||||
}
|
||||
super.render(delta)
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.unciv.ui.newgamescreen
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox
|
||||
@ -9,7 +8,7 @@ import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener
|
||||
import com.badlogic.gdx.utils.Array
|
||||
import com.unciv.logic.MapSaver
|
||||
import com.unciv.models.metadata.GameSpeed
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.ruleset.VictoryType
|
||||
import com.unciv.models.ruleset.tech.TechEra
|
||||
import com.unciv.models.translations.tr
|
||||
@ -20,7 +19,6 @@ class NewGameScreenOptionsTable(val newGameScreen: NewGameScreen, val updatePlay
|
||||
: Table(CameraStageBaseScreen.skin) {
|
||||
val newGameParameters = newGameScreen.newGameParameters
|
||||
val mapParameters = newGameScreen.mapParameters
|
||||
val baseRuleset = newGameScreen.ruleSet.clone()
|
||||
val ruleset = newGameScreen.ruleSet
|
||||
|
||||
init {
|
||||
@ -221,46 +219,22 @@ class NewGameScreenOptionsTable(val newGameScreen: NewGameScreen, val updatePlay
|
||||
|
||||
|
||||
fun addModCheckboxes() {
|
||||
|
||||
val modFolders = Gdx.files.local("mods")
|
||||
if(!modFolders.exists()) return
|
||||
val loadableMods = ArrayList<Ruleset>()
|
||||
|
||||
for (modFolder in modFolders.list()) {
|
||||
if (modFolder.list().any { it.name() == "jsons" }) {
|
||||
val modRuleset = Ruleset(false)
|
||||
|
||||
try {
|
||||
modRuleset.load(modFolder.child("jsons"))
|
||||
modRuleset.name = modFolder.nameWithoutExtension()
|
||||
loadableMods.add(modRuleset)
|
||||
|
||||
} catch (ex: Exception) {
|
||||
print(ex.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
val modRulesets = RulesetCache.filter { it.key!="" }.values
|
||||
if(modRulesets.isEmpty()) return
|
||||
|
||||
fun reloadMods(){
|
||||
ruleset.clearExceptModNames()
|
||||
ruleset.add(baseRuleset)
|
||||
for(modName in ruleset.mods){
|
||||
val correspondingMod = loadableMods.first { it.name==modName }
|
||||
ruleset.add(correspondingMod)
|
||||
ruleset.clear()
|
||||
ruleset.add(RulesetCache.getComplexRuleset(newGameParameters.mods))
|
||||
}
|
||||
}
|
||||
|
||||
if(loadableMods.isEmpty()) return
|
||||
|
||||
|
||||
add("{Mods}:".tr()).colspan(2).row()
|
||||
val modCheckboxTable = Table().apply { defaults().pad(10f) }
|
||||
for(mod in loadableMods){
|
||||
for(mod in modRulesets){
|
||||
val checkBox = CheckBox(mod.name,CameraStageBaseScreen.skin)
|
||||
checkBox.addListener(object : ChangeListener() {
|
||||
override fun changed(event: ChangeEvent?, actor: Actor?) {
|
||||
if(checkBox.isChecked) ruleset.mods.add(mod.name)
|
||||
else ruleset.mods.remove(mod.name)
|
||||
if(checkBox.isChecked) newGameParameters.mods.add(mod.name)
|
||||
else newGameParameters.mods.remove(mod.name)
|
||||
reloadMods()
|
||||
updatePlayerPickerTable()
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.unciv.ui.utils
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.graphics.Texture
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas
|
||||
@ -11,8 +12,8 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.Drawable
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable
|
||||
import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.models.ruleset.Nation
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.tile.ResourceType
|
||||
|
||||
object ImageGetter {
|
||||
@ -24,6 +25,7 @@ object ImageGetter {
|
||||
// So, we now use TexturePacker in the DesktopLauncher class to pack all the different images into single images,
|
||||
// and the atlas is what tells us what was packed where.
|
||||
var atlas = TextureAtlas("game.atlas")
|
||||
var ruleset = Ruleset()
|
||||
|
||||
// We then shove all the drawables into a hashmap, because the atlas specifically tells us
|
||||
// that the search on it is inefficient
|
||||
@ -33,14 +35,26 @@ object ImageGetter {
|
||||
setTextureRegionDrawables()
|
||||
}
|
||||
|
||||
fun getRuleSet() = UncivGame.Current.ruleset
|
||||
|
||||
fun setTextureRegionDrawables(){
|
||||
textureRegionDrawables.clear()
|
||||
// These are the srawables from the base game
|
||||
for(region in atlas.regions){
|
||||
val drawable =TextureRegionDrawable(region)
|
||||
textureRegionDrawables[region.name] = drawable
|
||||
}
|
||||
|
||||
// These are from the mods
|
||||
for(mod in ruleset.mods){
|
||||
val modAltasFile = Gdx.files.local("mods/$mod/game.atlas")
|
||||
if (modAltasFile.exists()) {
|
||||
val modAtlas = TextureAtlas(modAltasFile)
|
||||
for (region in modAtlas.regions) {
|
||||
val drawable = TextureRegionDrawable(region)
|
||||
textureRegionDrawables[region.name] = drawable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshAltas() {
|
||||
@ -112,13 +126,13 @@ object ImageGetter {
|
||||
return getImage("OtherIcons/Stop")
|
||||
if(improvementName.startsWith("StartingLocation ")){
|
||||
val nationName = improvementName.removePrefix("StartingLocation ")
|
||||
val nation = getRuleSet().nations[nationName]!!
|
||||
val nation = ruleset.nations[nationName]!!
|
||||
return getNationIndicator(nation,size)
|
||||
}
|
||||
|
||||
val iconGroup = getImage("ImprovementIcons/$improvementName").surroundWithCircle(size)
|
||||
|
||||
val improvement = getRuleSet().tileImprovements[improvementName]!!
|
||||
val improvement = ruleset.tileImprovements[improvementName]!!
|
||||
when {
|
||||
improvement.food>0 -> iconGroup.circle.color= foodCircleColor
|
||||
improvement.production>0 -> iconGroup.circle.color= productionCircleColor
|
||||
@ -131,8 +145,8 @@ object ImageGetter {
|
||||
}
|
||||
|
||||
fun getConstructionImage(construction: String): Image {
|
||||
if(getRuleSet().buildings.containsKey(construction)) return getImage("BuildingIcons/$construction")
|
||||
if(getRuleSet().units.containsKey(construction)) return getUnitIcon(construction)
|
||||
if(ruleset.buildings.containsKey(construction)) return getImage("BuildingIcons/$construction")
|
||||
if(ruleset.units.containsKey(construction)) return getUnitIcon(construction)
|
||||
if(construction=="Nothing") return getImage("OtherIcons/Stop")
|
||||
return getStatIcon(construction)
|
||||
}
|
||||
@ -180,7 +194,7 @@ object ImageGetter {
|
||||
|
||||
fun getResourceImage(resourceName: String, size:Float): Actor {
|
||||
val iconGroup = getImage("ResourceIcons/$resourceName").surroundWithCircle(size)
|
||||
val resource = getRuleSet().tileResources[resourceName]!!
|
||||
val resource = ruleset.tileResources[resourceName]!!
|
||||
when {
|
||||
resource.food>0 -> iconGroup.circle.color= foodCircleColor
|
||||
resource.production>0 -> iconGroup.circle.color= productionCircleColor
|
||||
@ -204,7 +218,7 @@ object ImageGetter {
|
||||
|
||||
fun getTechIconGroup(techName: String, circleSize: Float): Group {
|
||||
var techIconColor = Color.WHITE
|
||||
when (getRuleSet().technologies[techName]!!.era().name) {
|
||||
when (ruleset.technologies[techName]!!.era().name) {
|
||||
"Ancient" -> techIconColor = colorFromRGB(255, 87, 35)
|
||||
"Classical" -> techIconColor = colorFromRGB(233, 31, 99)
|
||||
"Medieval" -> techIconColor = colorFromRGB(157, 39, 176)
|
||||
|
@ -53,6 +53,14 @@ internal object DesktopLauncher {
|
||||
settings.filterMin = Texture.TextureFilter.MipMapLinearLinear
|
||||
TexturePacker.process(settings, "../Images", ".", "game")
|
||||
|
||||
// pack for mods as well
|
||||
val modDirectory = File("../assets/mods")
|
||||
if(modDirectory.exists()) {
|
||||
for (mod in modDirectory.listFiles()!!){
|
||||
TexturePacker.process(settings, mod.path + "/Images", mod.path, "game")
|
||||
}
|
||||
}
|
||||
|
||||
val texturePackingTime = System.currentTimeMillis() - startTime
|
||||
println("Packing textures - "+texturePackingTime+"ms")
|
||||
}
|
||||
|
Reference in New Issue
Block a user