Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
647a24a
Add files via upload
PhiR42 May 27, 2020
6255dcb
Add files via upload
PhiR42 May 27, 2020
30d36ef
Update database.py
PhiR42 May 27, 2020
be210b6
Add files via upload
PhiR42 May 27, 2020
1212528
Add files via upload
PhiR42 May 27, 2020
e4f3f8f
Bugfix for guns annotation
PhiR42 May 28, 2020
c8f08c9
Support for modding textures
PhiR42 May 28, 2020
0f8bb98
Support for adding new textures
PhiR42 May 29, 2020
426d1bd
better platform support for finding spacehaven
PhiR42 May 29, 2020
bc60c18
maj
PhiR42 May 29, 2020
2cc0773
doc update
PhiR42 May 29, 2020
04150f5
better UI and bugfixes
PhiR42 May 29, 2020
047300f
More responsive UI
PhiR42 May 29, 2020
4a7ce3f
Enabling/disabling mods via the UI
PhiR42 May 29, 2020
fc6855c
Update spacehaven-modloader.py
PhiR42 May 29, 2020
671db40
Quicklaunch support
PhiR42 May 29, 2020
efbfb39
UI bugfix
PhiR42 May 30, 2020
9d4b4fb
maj gitignore
PhiR42 May 30, 2020
7d3df1e
Support for annotating texture assets in animations
PhiR42 May 30, 2020
fa0cff0
Changed xml attribute name
PhiR42 May 31, 2020
0d63545
Split extract and annotate into 2 buttons
PhiR42 May 31, 2020
06f613e
open the library directory
PhiR42 May 31, 2020
204be26
some more explainations
PhiR42 May 31, 2020
2787531
remove jar file from filetypes as it can't be launched
PhiR42 May 31, 2020
5f02334
setup & new version
PhiR42 May 31, 2020
6879aed
Bugfix when creating new texture packs
PhiR42 May 31, 2020
938b1ed
maj
PhiR42 May 31, 2020
132c39a
Support for breaking up haven and animations in multiple files
PhiR42 May 31, 2020
40e47b4
Better handling of game versions
PhiR42 May 31, 2020
92209f5
Fast launch when no mods are enabled
PhiR42 Jun 1, 2020
00583de
UI indication for mod enabled/disabled
PhiR42 Jun 1, 2020
cbd525c
Better annotations, support for modding game difficulty settings
PhiR42 Jun 1, 2020
934b78e
Update .gitignore
PhiR42 Jun 1, 2020
58ae256
added support for extra mod dirs in extra_mods_path.txt, more info.xm…
PhiR42 Jun 2, 2020
2fe034e
new info.xml tags
PhiR42 Jun 2, 2020
9d737f8
NEW VERSION
PhiR42 Jun 2, 2020
13114f9
Update .gitignore
PhiR42 Jun 2, 2020
f7d902c
cleanup!
PhiR42 Jun 2, 2020
25e3c61
Update README.md
PhiR42 Jun 2, 2020
03a4d8b
Delete oldPrefs
PhiR42 Jun 2, 2020
904b770
Delete temp.lib
PhiR42 Jun 2, 2020
632ac44
Delete user
PhiR42 Jun 2, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,11 @@ build/
dist/
.eggs/
spacehaven-modloader.spec
logs.txt
logs.txt
previous_spacehaven_path.txt
*.patch
*.orig
*.rej
quicklaunch_*.jar
quicklaunch_*/
extra_mods_path.txt
39 changes: 29 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ It is **not associated with Bugbyte or Space Haven in any way** other than that

## Getting Started

Download the latest release from [the releases page](https://github.com/anatarist/spacehaven-modloader/releases) and fire it up.
Download the [latest release](https://github.com/PhiR42/spacehaven-modloader/releases/download/0.0.7/spacehaven-modloader-win-amd64.zip) and fire it up.

![Screenshot](/tools/screenshot.png?raw=true)

Expand All @@ -22,33 +22,46 @@ spacehaven.jar
savegames/
...
mods/
artificial-plant/
info
library/
exterior-air-vent/
info
BetterToilets/
info.xml
library/
haven.xml
animations.xml
textures.xml
textures/
2283.png
...
```

4. Go back to the mod loader and make sure the mods you installed appear in the list. If they don't then you might not have installed them properly. Double check the folder structure.

5. When you're ready click "Launch Space Haven!" to play with mods. The mod loader will load the mods into the game, launch the game, and then unload them again when the game exits.

6. Once you've played with a given set of mods, the loader will keep a quick launch file for them. The next time they will load a lot faster. If you want to develop your own mod or tweak one, you should always click on "Clear QuickLaunch file" before running the game, so your changes are taken into account.

## Known issues

If you change the language or restart the game from within itself, the modloader will think the game has quit and will throw an error. Nothing permanent.

Running the game from the modloader will not load your cloud credentials correctly.

## Modding Guide

Mods are stored as a series of XML files in roughly the same format as the game's library.

You can take a look at the library by clicking the "Extract & annotate game assets" button. That will extract the game library from `spacehaven.jar` into `mods/spacehaven/` and open the folder.
You can take a look at the library by clicking the "Extract game assets" button. That will extract the game library from `spacehaven.jar` into `mods/spacehaven/` and open the folder.

The main file of interest is `library/haven.annotated`, which is an annotated copy of `library/haven`, which is the main game library. It contains definitions for most of the things in the game (buildings, items, ships, characters, objectives, generation parameters, etc). Also of interest are `library/texts`, `library/animations`, and `library/textures`.
Once that's done, you can also click "Annotate XML":
The main file of interest is `library/haven_annotated.xml`, which is an annotated copy of `library/haven`, which is the main game library. It contains definitions for most of the things in the game (buildings, items, ships, characters, objectives, generation parameters, etc). Also of interest are `library/texts`, `library/animations`, and `library/textures`.

`library/animations_annotated.xml` will be created as well, with the textures names for some of the individual blocks I have personnally used. You can add/change textures names by editing the the `textures_annotations.xml` file in the modloader directory. You should copy the corresponding line from `library/textures` and add an `_annotation` tag with the name that makes sense for you, after finding the textures of interest in `library/textures.exploded/`.

Mods follow the same folder structure and file format and should be reasonably obvious from the included sample mods.

Note that because mods are loaded by doing an id-wise merge with the base game library, only the following files and tags are currently supported:
- All definitions in `library/haven`
- `animations` in `library/animations`
- `t`s and `re`s in `library/textures`
- `t`s in `library/texts`


Expand Down Expand Up @@ -109,12 +122,18 @@ and looking up the `<name tid="869" />` we see the category is named:
</lifesupport>
```

To make life easier, the mod loader does these name lookups automatically in a few places and stores the results in `_name=""` attributes. This annotated version of the library is saved to `library/haven.annotated` and is (accordingly) a bit easier to navigate.
To make life easier, the mod loader does these name lookups automatically in a few places and stores the results in `_annotated_name=""` attributes. This annotated version of the library is saved to `library/haven.annotated.xml` and is (accordingly) a bit easier to navigate.


### Textures and Animations

Extracting and annotating game assets also decodes and explodes the game's textures into `library/textures.exploded`. The game's original packed textures are written to `library/textures.exploded/*.png` and the texture regions are written to `library/textures.exploded/*/*.png`, where the folder name is the texture ID and the filename is the region ID.

Regions definitions can be found in `library/textures` in `<re n="..." />` tags and are used in `library/animations` by the `<assetPos a="..." />` tags.
Regions definitions can be found in `library/textures.xml` in `<re n="..." />` tags and are used in `library/animations.xml` by the `<assetPos a="..." />` tags.

The loader can overwrite existing textures from the base game and add new ones. To overwrite a texture, simply place a file called `123.png` in a directory called `textures` (where 123 is the id of the texture you want to replace).

To add a new texture, you need to add a `<re n="..." t="..." />` entry in `library/textures.xml`. Textures are packed together in `.cim` files, which are defined in `<t i="..." />` entries. The `re` entry references the `t` entry through it's index `i`. Take care of choosing reasonably unique indexes to avoid conflicts. Once this is done, you can add the `123456.png` file to the `textures` directory.

When loading your mod, the modloader will output the png version of the packed texture file for debugging purposes. This will allow you to check proper placement for new textures, or correct merging for existing ones.

152 changes: 111 additions & 41 deletions loader/assets/annotate.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,35 @@

import ui.log


def annotate(corePath):
"""Generate an annotated Space Haven library"""

texture_names = {}
local_texture_names = ElementTree.parse("textures_annotations.xml", parser=XMLParser(recover=True))
for region in local_texture_names.findall(".//re[@n]"):
if not region.get("_annotation"):
continue
texture_names[region.get('n')] = region.get("_annotation")

animations = ElementTree.parse(os.path.join(corePath, "library", "animations"), parser=XMLParser(recover=True))
for assetPos in animations.findall('.//assetPos[@a]'):
asset_id = assetPos.get('a')
if not asset_id in texture_names:
continue
assetPos.set('_annotation', texture_names[asset_id])

annotatedPath = os.path.join(corePath, "library", "animations_annotated.xml")
animations.write(annotatedPath)
ui.log.log(" Wrote annotated annimations to {}".format(annotatedPath))

haven = ElementTree.parse(os.path.join(corePath, "library", "haven"), parser=XMLParser(recover=True))
texts = ElementTree.parse(os.path.join(corePath, "library", "texts"), parser=XMLParser(recover=True))

# Load texts

tids = {}
# Load texts
for text in texts.getroot():
tids[text.get("id")] = text.find("EN").text

def nameOf(element):
name = element.find("name")
if name is None:
Expand All @@ -27,54 +44,107 @@ def nameOf(element):
return ""

return tids[tid]


ElementRoot = haven.find("Element")
# Annotate Elements
for element in haven.find("Element"):
for element in ElementRoot:
mid = element.get("mid")

objectInfo = element.find("objectInfo")
if objectInfo is not None:
element.set("_name", nameOf(objectInfo))

element.set("_annotation", nameOf(objectInfo))
# Annotate basic products
# first pass also builds the names cache
elementNames = {}
for element in haven.find("Product"):
ProductRoot = haven.find("Product")
for element in ProductRoot:
name = nameOf(element) or element.get("elementType") or ""

element.set("_name", name)

if name:
element.set("_annotation", name)
elementNames[element.get("eid")] = name

# Annotate process products
for element in haven.find("Product"):

for item in haven.find("Item"):
name = nameOf(item) or item.get("elementType") or ""

if name:
item.set("_annotation", name)
elementNames[item.get("mid")] = name

# small helped to annotate a node
def _annotate_elt(element, attr = None):
if attr:
name = elementNames[element.get(attr)]
else:
name = elementNames[element.get("element", element.get("elementId"))]
if name:
element.set("_annotation", name)
return name

# construction blocks for the build menu
for me in ElementRoot:
for customPrice in me.findall(".//customPrice"):
for sub_l in customPrice:
_annotate_elt(sub_l)

# Annotate facility processes, now that we know the names of all the products involved
for element in ProductRoot:
processName = []

needs = element.find("needs")
if needs is not None:
for need in needs:
name = elementNames[need.get("element")]
need.set("_name", name)
processName.append(name)

for need in element.xpath("needs/l"):
name = _annotate_elt(need)
processName.append(name)

processName.append("to")

products = element.find("products")
if products is not None:
for product in products:
name = elementNames[product.get("element")]
product.set("_name", name)
processName.append(name)

processName = " ".join(processName)
if len(processName) > 2 and not element.get("_name"):

for product in element.xpath("products/l"):
name = _annotate_elt(product)
processName.append(name)

if len(processName) > 2 and not element.get("_annotation"):
processName = " ".join(processName)
elementNames[element.get("eid")] = processName
element.set("_name", processName)

for element in haven.find("Product"):
list = element.find("list")
if list is not None:
for process in list.find("processes"):
process.set("_name", elementNames[process.get("process")])

annotatedHavenPath = os.path.join(corePath, "library", "haven.annotated")
element.set("_annotation", processName)

#generic rule should work for all remaining nodes ?
for sub_element in haven.findall(".//*[@consumeEvery]"):
try:
_annotate_elt(sub_element)
except:
pass
# error on 446, weird stuff
#print(sub_element.tag)
#print(sub_element.attrib)

# iterate again once we have built all the process names
for process in ProductRoot.xpath('.//list/processes/l[@process]'):
process.set("_annotation", elementNames[process.get("process")])

for trade in haven.find('TradingValues').findall('.//t'):
try:
_annotate_elt(trade, attr = 'eid')
except:
pass

DifficultySettings = haven.find('DifficultySettings')
for settings in DifficultySettings:
name = nameOf(settings)

if name:
settings.set("_annotation", name)

for res in DifficultySettings.xpath('.//l'):
try:
_annotate_elt(res, attr = 'elementId')
except:
pass

for res in DifficultySettings.xpath('.//rules/r'):
try:
_annotate_elt(res, attr = 'cat')
except:
pass

annotatedHavenPath = os.path.join(corePath, "library", "haven_annotated.xml")
haven.write(annotatedHavenPath)
ui.log.log(" Wrote annotated spacehaven library to {}".format(annotatedHavenPath))
Loading