WorldGrower Developer Guide

Coding Standards
Changelog
Commit Messages
Release
Debugging
Overview
Coding Guidelines
Adding a new Action
Adding a new WorldObject
Adding a new Goal
Adding a new Image
Adding a new Conversation
Adding unit tests


Coding Standards


Use the Java Coding conventions: http://www.oracle.com/technetwork/java/javase/documentation/codeconvtoc-136057.html

Changelog


When adding changes, be sure to update CHANGES file.

Commit Messages


A good commit should not only contain good changes, but also include a helpful description of them for other developers, people tracking regressions, project maintainers, and even yourself in the future. There are many style guides on the Web describing best practices for documenting your Git commits.

Release


WorldGrower is released with Gradle 2.0.
Download Gradle from https://gradle.org/gradle-download/

run 'gradle -PdoRelease clean release' to create a release.

When creating a new release, a local install4j installation is needed.
The gradle.properties file needs to point the install4j install directory:
install4jHomeDir=C:/Program Files/install4j6

Debugging


To run in debug  mode, start WorldGrower with -DDEBUG=true.
This allows to view the properties of each WorldObject.
This also gives access to the commoners overview screen and other debug screens.

Overview

A WorldObject represents an object in the World and has properties.
Properties such as hit points, gold, name, etc.
All properties are declared in the Constants class and have types like integer, String, List of ids, etc.

Each WorldObject has a unique id, an int with which can be given to World instance to retrieve it.

Each intelligent WorldObject can perform actions (ManagedOperation) on other WorldObjects.
For example: person performs CutWoodAction on Tree
                      person performs TalkAction with other person

An intelligent WorldObject uses Goal instances to further its goals.
It uses WorldObjectPriorities to return multiple Goal instances.
The Goal instances at the beginning of those multiple Goal instances have a higher priority than the ones at the end.

Coding Guidelines


under construction

Adding a new Action


Adding a new Action/ManagedOperation is as follows:
Create a class implementing ManagedOperation or its subinterfaces under org.worldgrower.actions or subpackages.
There are several interfaces that extend ManagedOperation:
    - MagicSpell for magic spells
    - BuildAction for actions that build something

The execute method changes properties in performer and target

The isValidTarget method indicates what properties a target should have so that this
action can be executed on it. Nothing should be assumed about any properties the target has.
So it's best to check with WorldObject::hasProperty.

The distance method determine whether the circumstances are correct to execute the action.
This includes circumstances like location, having certain items in the inventory, etc.
When the distance is 0, the action can be executed.
When the distance is not 0, the action cannot be executed.
This method is used when executing a method, and also in the pathfinding algorithm A*.
When the performer needs to be next the target, it's recommended to use the Reach class.

The getArgumentRanges method returns ranges for the int[] args argument of the execute.
At this moment, the code only checks whether there are arguments or not.
This has implications for the user interface, as methods without argument ranges can be called automatically.
For actions that expect int[] args, the user interface needs a way for the user to pass them along.

The getSimpleDescription method describes the action in a simple command.

The getDescription method describes the action as it is used in a sentence like:
"She was " + action.getDescription()
So for example: "cutting wood", "talking to Adela", etc.

The readResolve needs needs to be present in order for serialization to work.

When the class is implemented, it needs to be added to the Actions class,
both as a constant and added to other similar actions.

If the action has int[] args and ArgumentRanges, it needs to be added to the GuiMouseListener class,
so that it appears in the popup menu.

Once an action has been added, it's best to create one or Goals that use the action.
That way, intelligent WorldObjects can also use the new action.

Adding a new WorldObject

A new WorldObject probably requires some new properties
New properties can be added to the Constants class.

When a WorldObject points to another worldObject, it usually uses the id.
That's why there are types like IdList, IdMap, etc.
WorldObject are sometimes deep copied, which is trickier with direct references to other WorldObject instances.

A WorldObject can have references to WorldObject instances that don't have an id.
Inventory items are examples of WorldObject instances that don't have an id.
That's because the World instance doesn't directly contain them.
A WorldObject with an inventory property contains them.

The method that creates the new WorldObject is placed in the org.worldgrower.generator package.
In that package are several classes that generate new WorldObjects like PlantGenerator or ItemGenerator.

Adding a new Goal

Create a new class under org.worldgrower.goal that implements the Goal interface.

The method calculateGoal returns an OperationInfo instance which best describes how to attain the goal.
It shouldn't change any state in WorldObjects or elsewhere.
The Goal can depend on subgoals, for example when inventory items are needed.
The calculateGoal method should test whether anything is missing, and if it is, return subgoals.
If there is no way for the goal to be attained, null can be returned.

The method goalMetOrNot is used to record whether some demands aren't being met.
For example: if the food goal isn't met, it can be added to the demands property.
The demands property is used to determine what goods are in demand,
and is used when intelligent WorldObjects choose a profession.

The method isGoalMet indicates whether this goal is being met.
If it isn't being met, the calculateGoal method is called.

Normally one goal is finished before the next goal is started.
If the method isUrgentGoalMet returns false, the current goal is aborted and the urgent goal is calculated.

The method getDescription retrun a description of the goal like:
"I'm" + getDescription
For example: I'm looking for food

The evaluate method indicates how much success there is in attaining the goal.
A higher number is better.
If someone does something which lowers this number, the person for which this goal is meant becomes unhappy.

Once the class is implemented, it can be added to the Goals class.
After that, the constant in the Goals class can be added to other classes that provide Goals like
    - CommonerWorldEvaluationFunction
    - any of the Profession implementations if the Goal is related to a profession

Adding a new Image


Adding a new image means adding a new entry to ImageIds
and add the code to read in the image to ImageInfoReader.

Adding a new Conversation


Create a class under org.worldgrower.conversation or its subpackages that implements Conversation.

The method getQuestionPhrases returns  possible Questions for this conversation.
A Question contains a questionPhrase, the text that is actually presented to the target.
It also contains a subject, which is a WorldObject that is sent to the target when it has to reply.
It contains a similar field for HistoryItem, for when the conversion discusses an event that has happened.
And an additionalValue in case a Question needs it.

The method getReplyPhrase returns a response, in case the question is asked to an intelligent WorldObject.
It's best to use the default method getReply like this:
final int replyId = ...;
return getReply(getReplyPhrases(conversationContext), replyId);
That way the replyPhrases aren't duplicated accross methods.

The method getReplyPhrases returns multiple Responses.
Each Response has an id, which is the replyId used in the method getReplyPhrase.
A response has the same fields as a question inclusing the historyItem and the subject.
It also has a boolean isPossible which indicates whether the Response is possible.

The method isConversationAvailable indicates whether the conversation is available.

The method getDescription describes the Conversation as if "I'm" is put before the description.
For example: I'm talking about my name

Adding unit tests


All tests are under the test folder and follow a similar package structure as the source tree.
If the class you want to add a test for is already being tested, the test can simply be added to that class.

If the class isn't yet being tested, it's best to add a new test class with prefix UTest.
To add the new class to the existing tests, just add it to the CompleteTestSuite class.