-
Notifications
You must be signed in to change notification settings - Fork 0
How to write an event
Events are separated broadly into two categories KNOWN and UNKNOWN. Known events are events that the user will be able to see and anticipate on their calendar. Unknown events are invisible to the user but are still spawned when the calendar is first initialized (at the start of the game / year) not "on the fly" as each day passes.
Additionally, all events follow this function signature
public static Outcome Event(DataManager, Requirements)
-
DataManager
holds all information about the league, player, and current game session. -
Requirements
holds the different objects the user will need to send in, for instance a child to participate in an event or a sum of money. -
Outcome
holds a status integer and outcome strings which will be displayed to the user upon completion of the Event
In this example we'll be writing an event in which a random grandchild is going on a field trip with a parent chaperone. Additionally, you'll need to provide funds to your grandchild so they can bring home a souvenir.
First step in writing a known event is to layout the basic design in the xml file: Resources\events.xml
The top level tag in this document is <events></events>
, to add an event use the tag directly beneath like so (remember to wrap ALL your attributes (name, id, type, etc) in double quotes ""):
<event type="1" id="5" name="Grandkids go on a field trip" description="{0} is going on a field trip! Additionally the school has asked for more chaperones so please provide one, {1}!" priority="2" req_children="11" req_parent="01" req_grandpa="00" req_money="01" req_accept="00" month="3" day="0" qualification="IS_RESPONSIBLE" age="6-15"/>
Let's break this event down by each "attribute" that is required:
-
type: 0 for unknown, 1 for known events, 2 for reserved system events
-
id: the unique numerical id for which to represent your event. It is very important that this is unique. It is recommended you work in your own space this way two people working on events and committing them won't have merge conflicts. To check how we currently distribute the ID numbers check the bottom of this page.
-
name: The name of the event that will be displayed to the user
-
description: A brief description of the event and what the user will need to provide. Note {0} denotes a empty space that can be filled in later. Each different name requires a new {#}. As shown, the user's name would go in {1}
-
priority: A number which describes how important the current event is. 0 is lowest and 2 is the highest. An event with a priority of 0 will be passed over silently with output appearing only in the mail panel. An event with priority of 1 will "pause" the current simulation to prompt the user for input before continuing. An event with priority 2 will stop the simulation completely. Presumably this is only for major events
-
req_children, req_parent, req_grandpa, req_money, req_accept: This is coded binary for the required inputs in our even. The "first" bit (bit on the right) represents if it is required for the event. The "second" (left) bit says if it is random or not. Please note 1 denotes true and 0 denotes false. For our event the user needs to provide a parent from their team and also an amount of money hence we have 01. Now the child going on this field trip will be randomly selected by the computer. Hence we have 11 coded for them.
-
month and day: additionally programmers can specify the specific month and day the event occur (please remember we only have 28 day month currently). Moreover, the DAY field is OPTIONAL. Leaving this out or setting it to 0 will result being "seasonal" and a random day during the specified month will be chosen for the event.
-
qualification (optional): we now have a basic qualification system in place. These are simple attributes (denoted by a unique string, int pair) granted to characters to add more flavor. For example we may have an event where a child in your family needs the ON_FOOTBALL_TEAM qualification to complete. The Event system is robust enough that it can silently execute this event and only display output if someone in your family qualifies (and the player is none the wiser). On this example having a qualification doesn't quite make sense but we include it for completeness. More on a qualified event at the end of this document.
-
age (optional): this is a range of ages, in our case 6 to 15, that a child needs to be in order to be eligible for this event. In this particular context it means that the system will only choose a child in this age range (inclusive of the endpoints). Please note this age range only applies to the child chosen for the event (not parents or grandparents).
NOTE: When trying to select a random child with an age restriction for your event the system will occasionally not be able to find any eligible children. In this case the requirement.Child
will be null
it is up to you to decide how to handle the user not having an eligible child (punishing them or otherwise) but more importantly to check this condition so the program does not crash
Now we can begin programming the function which will interact with data.
Open EventManager.cs
and scroll to the bottom. You will see functions of the form "Event#". The # corresponds to the event id you gave in your xml, so find the numerical correct place to put your event and use the function signature:
public static Outcome Event5(DataManager manager, Requirement requirements)
{
}
By the time this function is called the Requirement
object will already be initialized with our needed Parent and money so we don't need to worry about them and can assume they have already been initialized. You can access them using properties requirements.Parent
and requirements.Money
. Additionally, because this event chose a random child requirements.Child
will also be initialized. It is up to the developer to know what they need to get from the Requirements object.
DataManager
is a complex datastructure with literally every piece of data in the game. Check out DataManager.cs
for the various properties you can work with.
Now lets write the body of our function. If our parents intelligence and popularity have a sum of greater than 110 and our child's popularity is > 50 we can say the event is a success. Additionally if your child has > $50 he can buy a kickass souvenir.
int successes = 0;
List<string> outcome = new List<string>();
Outcome returnObj = new Outcome();
if(requirements.Parent.Popularity + requirements.Parent.Intelligence > 110)
{
requirements.Parent.Popularity += 5;
string outcome += outcome.Add(String.Format("{0} gained popularity ", requirements.Parent.Name));
successes++;
}
else
{
//some parent failure outcomes
}
if(requirements.Child.Popularity > 50 && requirements.Money > 50)
{
requirements.Child.Popularity += 10
successes+=2;
//some strings for child gains
}
else if (requirements.Child.Popularity > 50)
{
requirements.Child.Popularity += 5
successes++;
//some strings for child gains / loses, etc
}
//event can be as complex as you want...continuing onwards
manager.PlayerFamily.Grandpa.Money -= requirements.Money
if (successes >= 2)
{
string outComeString = string.Join(", ", outcome);
returnObj.Status = (int)Enums.EventOutcome.SUCCESS;
returnObj.OutcomeDescription = String.Format("{0} had a great time at the field trip, {1}", requirements.Child.Name, outComeString)
}
else
{
returnObj.Status = (int)Enums.EventOutcome.FAILURE;
returnObj.OutcomeDescription = "etc, etc";
returnObj.Mail = new Mail();
returnObj.Mail.Message = "..."
}
return returnObj;
Above is the function body, pay special attention to strings. Events don't have to be concrete pass or fail, depending on different control flows it can go many different ways and have many different varied outcomes. If you want a random integer check out Constants.RANDOM.Next(int minValue, int maxValue)
NOTE: Objects in requirements are passed by reference this means that when you change an object in the requirements (e.g. a Parent or Child stat) is is immediately reflected in the DataManager. So you don't have to worry about finding the matching Child or Parent object in the DataManager, you can just directly change it from requirements. Also know this means any changes you make will be permanent so only edit the stats if you really need to.
NOTE: The Outcome.Status
field is not as important as the Outcome.OutcomeDescription
field. While we may use the Outcome Status int later for more dynamic events currently only the Outcome.OutcomeDescription
will be displayed to the user! So it is important to alert the user to what happened in detail. Outcome.Mail
is the "brief" or "story element" kind of message that will automatically be shown / remain in the player's mailbox so they can recall what has happened. Checkout Mail.cs
to get all the member variables. Off the top of my head you'll need to add Mail.Sender
, Mail.Subject
, Mail.Message
, and Mail.Date
to get the date use dataManager.Calendar.GetCurrentDate()
(or something very similar). Alternatively you can use Mail.StringDate
to customize the date it was "sent", be creative~
Now we'll look at a random event. They are extremely similar to known events except for how they're declared in the xml file:
<event type="0" id="1" name="Grandpa wins the lottery" description="You won the lottery! con fucking gratys" priority="1" chance="0.2" req_children="00" req_parent="00" req_grandpa="00" req_money="00" req_accept="00"/>
Key differences:
-
Event type is 0 for "hidden/unknown"
-
chance is a new field not on a known event. The chance is a decimal of how likely this event is to happen on any given day. Here our chance is 0.2, or 20%. Please remember there are 336 days in a year. If the chance was 1% it would still occur on average about 3 times a year
-
month: Note that day is absent (that would inherently make the event known) but month is not. You can program an event to happen only on a specific month range. So if you type month="1-3" then the event will only occur in January, February, and March.
Other than that it is the same as writing a known event, as long as the Event function matches the event ID and function signature you will be good to go!
The last type of event is a system event. It has type "2". An example of a system event is the weekly_stat_upgrade. Additionally, these events use the reserved ids 0-100
First step in writing a qualification event is to write down a qualification. Open up Resources\qualifications.xml
and take a look. You'll want to add a new <qualification />
tag like so:
<qualification name="ON_FOOTBALL_TEAM" id="3" display_name="On the football team!" icon="qualification_star_icon"/>
As you can see, our qualification has both a unique id and name. Make sure what you're looking for isn't already in the file.
display_name
is what is show when the user hovers over the icon
in family manager. We don't have sprites for all qualifications atm and the default is just qualification_star_icon
so use that for all of them for now
Next in events.xml
we need to add the qualification string to the event we are looking to add under a qualification
attribute, e.g. qualification="ON_FOOTBALL_TEAM"
Next lets program an event that can only happen if a child can has this qualification. Children can be given qualifications by using DataManager.PlayerFamily.Children[0].AddQualification(Qualification.GetQualificationByString("ON_FOOTBALL_TEAM");
Additionally, Qualification has another method Qualification.GetQualificationString(int qual)
that can be used to go the other way.
The Simulation loop will check the event on the current day and place a random "eligible" child/parent/grandpa in the Requirements object (note eligibility is qualification and, if applicable, age).
NOTE: Currently if there are no qualification eligible characters for what you are requesting the system will simply pass over your event silently.
These are the current event id namespaces, please use the corresponding ids as specified below.
- 0-100: reserved (do not use)
- 101-1000: Kevin
- 1001-2000: Patrick
- 2001-3000: Christoher
UPDATE: "we are changing the way event descriptions are done. Now instead of {0} {1} etc you'll use our custom wildcards {G} (grandpa) {EG} (enemy grandpa aka random grandpa) {C} (random child) {P} (random parent) and they will be filled in as appropriate. As such avoid gendered pronouns in event description. Too much of a hassle to deal with and not that big of a deal to live without"
NOTE: Enums.EventOutcome.PASS will make NO event outcome appear, use this when you want to "abort" the event but MAIL will always be sent. So if you want to pass and event and send a mail message that is 100% possible.
NOTE: events with priority 0 will not have event outcome textboxes, they will only send messages to the mailbox
NOTE: "If the event has no input requirements than no description is displayed. I figured at that point the event outcome is both the outcome and description. otherwise it'd be like "x is going to the park" and all youd do was click ok then you'd see "x went to the park!"" Meaning do not write a description for events with no inputs, save it for EventOutcome.OutcomeDescription
. There is an exception to this rule: when the priority of an event == 2 (highest) then the description is always displayed, it is helpful for major events to build tension and the like.
UPDATE: Certain events can be blacklisted by year or forever. This means that the event won't occur in the current calendar year again (not a 365 day cooldown!) or ever again. The return status for these are SUCCESS_BLACKLIST_YEAR
, SUCCESS_BLACKLIST_FOREVER
, FAILURE_BLACKLIST_YEAR
, FAILURE_BLACKLIST_FOREVER
. Additionally there are also PASS blacklists. These behave just like the regular PASS (meaning no output displayed, only mail sent) except the event is blacklisted. They are PASS_BLACKLIST_YEAR
and PASS_BLACKLIST_FOREVER
HINT: If you have an event classified as "hidden" and 'chance="0" and month="0"
then the event will never occur. This is useful because you can then call DataManager.Calendar.ScheduleEventInXDays(EventManager.GetEventById([MY EVENT ID INT]), [X NUM DAYS])
to essentially create a "follow" up event that can only be called by you!
Consider the following scenario. An event needs inputs such as children and money but the user's intelligence needs to be > 50 for anything to happen with these. The system will bother the person for input, and then when the event returns PASS
because they failed the intelligence check they will be confused because no output will show. Rather than do this, first schedule a hidden event with the date and other hidden requirements called [BLANK]_EVENT_SKILLCHECK
. Since this event has no input requirements it will silently execute and the user won't know anything happened. If the user passes use DataManager.Calendar.ScheduleEventInXDays
to add your actual event to be the very next day (in this case it would be a hidden event with chance=0, aka unexecutable unless we call it). The user will then see the input and be able to execute the event as normal.
NOTE: this technique only applies to skill check events that
- Don't punish the user or have adverse outcomes if they fail the skillcheck event (this only applies to ones where it PASS-es)
- Have input requirements from the user. If the event selects only random input requirements then it will still execute silently if the user passes. This technique is to avoid confusion when the user sees an input panel and then nothing happens because the event passes