Skip to content

How to write an event

Christopher Carney edited this page Jul 27, 2016 · 31 revisions

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

KNOWN EVENTS:

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.

XML WRITING

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

PROGRAMMING THE FUNCTION

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~

UNKNOWN (random) EVENTS

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!

RESERVED (system) EVENTS

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

QUALIFICATION EVENTS

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" hidden="false"/>

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_iconso use that for all of them for now

hidden is if the user can see it in the family panel or not. You can use hidden qualifications like flags for player choice. For example: in "Grandpa rigs the election" you present the user with a choice to rig the election or not. This event will then use Calendar.ScheduleEventInXDays() to fetch the "Grandkid runs for student council" event and schedule it for whenever. If the user selected "rig the election" then the "Grandpa rigs the election" event would grand the grandpa the hidden qualification "ELECTION_RIGGER". In "Grandkid runs for student counci" the event will check for this flag and have very different outcomes based on if it is there or not. make sure to remove these "hidden"/"temporary" qualifications ASAP if you aren't going to use them again (a la at the end of the "student council election" event because if the qualification is forgotten about it could carry over into next year's events and possibly cause issues.

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.

ABILTIES:

Abilities are a special kind of event. They require:

  • id from your namespace
  • type where type = 3, for ABILITY
  • name
  • description
  • ability_name
  • ability_description
  • cooldown in days
  • picture string name of the associated picture sprite (without the .png or extension)
  • full requirement object (all req_children, req_parent, etc, age range if applicable, qualification if applicable)

Note the subtle difference between ability_name/ability_description and event name/description. Once all these fields are present you can program your function like any other event, going off the assumption that all the fields that you need are fulfilled.

CURRENT EVENT ID NAMESPACES:

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

STAT CONSTANTS:

We are trying to standardize our stat gains and upgrades so it will be easy to re balance and test. This is how the big boys like Icefraud does it. Constants to use for event characters are found in Constants.Character. Consider the following code re-write:

requirement.Child.Intelligence += 10;

should be written as

requirement.Child.Intelligence += Constants.Character.MAJOR_STAT_CHANGE_AMOUNT;

There are also constants for growth rates and pride amounts. Please don't mix up STAT_CHANGE and STAT_GROWTH amounts that would be bad.

NOTE: If you feel like your event deserves a little something extra like Constants.Character.MAJOR_STAT_CHANGE_AMOUNT + 1, feel free to do so but do this very sparingly. The idea is to make re-balancing easy later.

NOTE: Do NOT confuse the Constants.Characters with Constants.League. The variables in League are to be used for the AI only because it will need to be balanced much differently.

Important Notes and Updates:

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:

  1. Don't punish the user or have adverse outcomes if they fail the skillcheck event (this only applies to ones where it PASS-es)

  2. 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