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.