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:
Yair Morgenstern
2020-01-05 22:11:10 +02:00
parent 6a9de727ad
commit 7462aae94c
14 changed files with 110 additions and 74 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -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()
}

View File

@ -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

View File

@ -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)

View File

@ -20,4 +20,5 @@ class GameParameters { // Default values are the default new game
var startingEra = TechEra.Ancient
var isOnlineMultiplayer = false
var mods = HashSet<String>()
}

View File

@ -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
}
}

View File

@ -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()

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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)

View File

@ -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")
}