var PARAMETERS = {
    "timedMemories": 1, 
    "focusPanelKey": "focusPanel", 
    "dayTicks": 150, 
    "currencyBoost": "currencyBoost", 
    "startEntities": [
        "gameStateEntity", 
        "self", 
        "day", 
        "controlPanel", 
        "chapter1", 
        "autosaver", 
        "trainPathMakingPanel", 
        "utilityPanel", 
        "dayPanel", 
        "focusPanel", 
        "buildPanel", 
        "cursor", 
        "currencyRenderEntity", 
        "difficultySetting", 
        "tutorialTextWakeUp"
    ], 
    "damageIndicator": "damageIndicator", 
    "trainPathMakingPanel": "trainPathMakingPanel", 
    "lostControl": "lostControl", 
    "baseButtonName": "baseButton", 
    "player": "player", 
    "buildingConnect": 1, 
    "path": "path", 
    "creepHit": [
        "lostControl", 
        "selfDamageEffect", 
        "disableMemories"
    ], 
    "interactionKeys": [
        "self"
    ], 
    "currencyRenderEntity": "currencyRenderEntity", 
    "size": [
        13, 
        23
    ], 
    "buildPanel": "buildPanel", 
    "settingsPanel": "settingsPanel", 
    "chapterPanel": "chapterPanel", 
    "selfExtension": "selfExtension", 
    "memoryConstruction": "memoryConstruction", 
    "self": "self", 
    "carriage": "carriage", 
    "buildablePaths": 0, 
    "ensureExistence": [
        "difficultySetting"
    ], 
    "randomMenuMarker": "randomMenuMarker", 
    "memory": "memory", 
    "memoryStopGoal": "memoryStopGoal", 
    "ticksPerTick": [
        2, 
        5
    ], 
    "defaultHeldOpinions": [
        "acquaintance"
    ], 
    "towerPanel": "entityPanel", 
    "selfDeadMenu": "breakdownMenu", 
    "difficultySetting": "difficultySetting", 
    "junction": "junction", 
    "extensionMonitorEnabled": 0, 
    "trainPanel": "trainPanel", 
    "trainPathMaker": "trainPathMaker", 
    "newParticle": "newParticle", 
    "openMenu": "openMenu", 
    "gameStageStartMenu": "gameStageStartMenu", 
    "towerFlash": "towerFlash", 
    "train": "train", 
    "emotionSource": "emotionSource", 
    "goalComplete": "goalComplete", 
    "allTrainPanel": "allTrainPanel", 
    "maxExperience": 8, 
    "startEntityGroups": [
        "emotionState", 
        "emotionSentinel", 
        "emotionCurrencyTicker", 
        "emotionEntityMaker", 
        "currencyConverter", 
        "traitMaker", 
        "notificationCurrency", 
        "emotionResourceMap", 
        "setPanel", 
        "selfExtension", 
        "currencyAction", 
        "personCurrencyAction", 
        "steamAchievement"
    ], 
    "day": "day", 
    "gameStateEntity": "gameStateEntity", 
    "addPoints": "addPoints", 
    "bullet": "bullet", 
    "sectors": [
        4, 
        6
    ], 
    "currencyRequest": "currencyRequest", 
    "clickText": "clickText", 
    "resourceModifiers": 0, 
    "traitPanel": "traitPanel", 
    "toast": "toast", 
    "utilityPanel": "utilityPanel", 
    "carryTrain": "carryTrain", 
    "allGoalPanel": "allGoalPanel", 
    "construction": "construction", 
    "allBuildingPanel": "allBuildingPanel", 
    "notification": "notification", 
    "autosaveKey": "autosave_sleeper", 
    "singleUseTrainPath": "singleUseTrainPath", 
    "trainPath": "trainPath", 
    "creepSelfDamage": 0
};

var allArgs = {
    "memory_confessFear": {
        "image": "trait-7533f2", 
        "memoryEmotions": [
            "fear"
        ], 
        "key": "memory_confessFear", 
        "name": "Fear of Confession"
    }, 
    "unpackGoal": {
        "key": "unpackGoal", 
        "randomPosition": 0, 
        "name": "Unpack", 
        "image": "trait-ede9e6_goal_goalUnpack-000000", 
        "startEntityKey": "adult", 
        "currencyRequested": {
            "chore": 2
        }, 
        "entitiesToMake": [
            "paintButton", 
            "rentMaker", 
            "readHomeGoal", 
            "exploreGoal", 
            "tutorialPreDefense"
        ], 
        "goalMenuKey": "unpackMenu"
    }, 
    "applyTower": {
        "image": "tower_motivation", 
        "activeImage": "tower_motivation_activeOutline-0099ff", 
        "name": "Be Fueled By", 
        "key": "applyTower", 
        "description": "Converts frustration -> energy"
    }, 
    "rentMenu": {
        "image": "trait-ede9e6_goal_goalPayRent-000000", 
        "name": "COMPLETED - Pay Rent", 
        "key": "rentMenu"
    }, 
    "chapter4Menu": {
        "image": "activeHex", 
        "completeText": "You can get used to anything", 
        "name": "Chapter 4 - The New Normal", 
        "key": "chapter4Menu"
    }, 
    "memory_prideJoy": {
        "image": "trait-e6e617", 
        "memoryEmotions": [
            "joy"
        ], 
        "key": "memory_prideJoy", 
        "name": "Doing Good"
    }, 
    "dateSad": {
        "emotionsToAdd": {
            "sadness": 1
        }, 
        "name": "Date Going Poorly", 
        "image": "tower_memoryDateSad-000000", 
        "key": "dateSad", 
        "memoryKey": "memory_dateSad", 
        "emotionTags": "self", 
        "resourceKey": "dateSadMineConstruction", 
        "activeImage": "tower_memoryDateSad-000000_activeOutline-0099ff"
    }, 
    "walkTower": {
        "name": "Walk", 
        "image": "tower_towerWalk", 
        "damageMultipliers": {
            "anger": 1, 
            "joy": 1, 
            "sadness": 1, 
            "fear": 1
        }, 
        "key": "walkTower", 
        "activeImage": "tower_towerWalk_activeOutline-0099ff", 
        "description": "Uses energy to calm all emotions"
    }, 
    "disavowSadnessMineConstruction": {
        "key": "disavowSadnessMineConstruction", 
        "constructionIdentifier": "disavowSadnessMine", 
        "name": "Alienation", 
        "image": "tower_memoryMissHome-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Alienation"
    }, 
    "parkDateMenu": {
        "image": "goal_goalPark-008f90-000000", 
        "completeText": "More fun than you deserve", 
        "name": "COMPLETED - Amusement Park Date", 
        "key": "parkDateMenu"
    }, 
    "restaurantPrepMenu": {
        "image": "goal_goalRestaurant-008f90-000000", 
        "name": "COMPLETED - Prepare To Eat Out", 
        "key": "restaurantPrepMenu"
    }, 
    "fearResourceModifier": {
        "doubleResources": [
            "energyMine", 
            "thoughtMine"
        ], 
        "stopResources": [
            "fantasyMine", 
            "daydreamMine", 
            "frustrationMine"
        ], 
        "image": "modifier-ff00ff", 
        "key": "fearResourceModifier"
    }, 
    "memory_dateFail": {
        "image": "trait-013d70", 
        "memoryEmotions": [
            "sadness"
        ], 
        "key": "memory_dateFail", 
        "name": "Date Wasn't Perfect"
    }, 
    "ringExcite": {
        "emotionsToAdd": {
            "joy": 3
        }, 
        "name": "Excitement", 
        "image": "tower_memoryRing", 
        "key": "ringExcite", 
        "memoryKey": "memory_ringExcite", 
        "emotionTags": "self", 
        "resourceKey": "ringExciteMineConstruction", 
        "activeImage": "tower_memoryRing_activeOutline-0099ff"
    }, 
    "thoughtMine": {
        "name": "Thought Mine", 
        "image": "tower_mineThought", 
        "sourceTrait": "intelligent", 
        "key": "thoughtMine", 
        "resourceKeys": [
            "thought"
        ], 
        "activeImage": "tower_mineThought_activeOutline-0099ff"
    }, 
    "understandTower": {
        "image": "tower_understand", 
        "activeImage": "tower_understand_activeOutline-0099ff", 
        "name": "Understand", 
        "key": "understandTower", 
        "description": "Converts thought -> understand"
    }, 
    "fantasyMine_personDConstruction": {
        "key": "fantasyMine_personDConstruction", 
        "constructionIdentifier": "fantasyMine_personD", 
        "name": "Constructing - Fantasize About", 
        "image": "trait-aaaaaa99_tower_mineFantasy", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Fantasize About", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "dating": {
        "buttonKeys": [
            "dateButton"
        ], 
        "name": "dating", 
        "key": "dating", 
        "aliveEntities": {
            "spendTimeGoal": 3, 
            "justTalkGoal": 3
        }
    }, 
    "joyResourceModifier": {
        "doubleResources": [
            "socialMine", 
            "fantasyMine", 
            "daydreamMine", 
            "impressMine", 
            "energyMine"
        ], 
        "stopResources": [
            "industryMine", 
            "thoughtMine", 
            "homeThoughtMine", 
            "painMine"
        ], 
        "image": "modifier-ffff00", 
        "key": "joyResourceModifier"
    }, 
    "energyMine": {
        "name": "Energy Spring", 
        "image": "tower_energy", 
        "sourceTrait": "adult", 
        "key": "energyMine", 
        "resourceKeys": [
            "energy"
        ], 
        "activeImage": "tower_energy_activeOutline-0099ff"
    }, 
    "goalHelpInfoPanel": {
        "components": [
            "worldGoalComponent"
        ], 
        "helpLines": [
            "These are things that your character wants.", 
            "They are completed by connecting it to the rest of the mind and generating the currencies that it requests."
        ], 
        "name": "Goals", 
        "key": "goalHelpInfoPanel"
    }, 
    "buyTimeMenu": {
        "image": "goal_goalBuyTime-008f90-000000", 
        "name": "COMPLETED - Buy Time", 
        "key": "buyTimeMenu"
    }, 
    "killMotherland": {
        "traitToKill": "motherland", 
        "key": "killMotherland"
    }, 
    "tutorialDefenseTab": {
        "nextEntities": [
            "tutorialBuildPaint", 
            "tutorialBuildPaint2"
        ], 
        "text": "And go to the tab for defenses", 
        "yOffset": 190, 
        "entityToMark": "adult", 
        "key": "tutorialDefenseTab", 
        "xOffset": 410
    }, 
    "ringExciteMineConstruction": {
        "key": "ringExciteMineConstruction", 
        "constructionIdentifier": "ringExciteMine", 
        "name": "Excitement", 
        "image": "tower_memoryRing-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Excitement"
    }, 
    "dateGreatMineConstruction": {
        "key": "dateGreatMineConstruction", 
        "constructionIdentifier": "dateGreatMine", 
        "name": "Date Was Great", 
        "image": "tower_memoryDateGood-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Date Was Great"
    }, 
    "fearTickModifier": {
        "tickFactor": 1.25, 
        "distractFactor": 0, 
        "image": "modifier-ff00ff", 
        "timeToDeath": 15, 
        "key": "fearTickModifier"
    }, 
    "flirtAction_personD": {
        "currenciesRequired": [
            "social_personD"
        ], 
        "currenciesToMake": [
            "flirt_personD"
        ], 
        "towerKey": "flirtTower_personD", 
        "name": "flirt - The One You Admire", 
        "key": "flirtAction_personD"
    }, 
    "confessFearMineConstruction": {
        "key": "confessFearMineConstruction", 
        "constructionIdentifier": "confessFearMine", 
        "name": "Fear of Confession", 
        "image": "tower_memoryConfession-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Fear of Confession"
    }, 
    "memory_dateGood": {
        "image": "trait-e6e617", 
        "memoryEmotions": [
            "joy"
        ], 
        "key": "memory_dateGood", 
        "name": "Date Going Well"
    }, 
    "disavowalTower": {
        "image": "tower_towerDisavowal", 
        "activeImage": "tower_towerDisavowal_activeOutline-0099ff", 
        "name": "Disavow", 
        "key": "disavowalTower", 
        "description": "Converts will + energy + frustration -> disavowal"
    }, 
    "shameMineConstruction": {
        "key": "shameMineConstruction", 
        "constructionIdentifier": "shameMine", 
        "name": "Shame", 
        "image": "tower_memoryShame-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Shame"
    }, 
    "tutorialTextAdultPanel": {
        "nextEntities": "tutorialPlaceEnergy", 
        "text": "You can get energy with this.", 
        "yOffset": 340, 
        "entityToMark": "adult", 
        "key": "tutorialTextAdultPanel", 
        "xOffset": 710
    }, 
    "fear": {
        "emotion": "fear", 
        "name": "fear", 
        "image": "creep-ff00ff", 
        "damage": 0, 
        "health": 10, 
        "key": "fear"
    }, 
    "cleanChoreGoal": {
        "key": "cleanChoreGoal", 
        "killOnTimeOut": [
            "cleanButton", 
            "job", 
            "cleanTower"
        ], 
        "randomPosition": 1, 
        "name": "Favor for Boss", 
        "makeOnTimeOut": "findJob", 
        "image": "trait-ede9e6_goal_goalCleanChore-000000", 
        "description": "Favor. Unpaid labor. Semantics.", 
        "startEntityKey": "city", 
        "currencyRequested": {
            "chore": 2
        }, 
        "goalMenuKey": "cleanChoreMenu", 
        "timeToDeath": 120
    }, 
    "restaurantPrepGoal": {
        "key": "restaurantPrepGoal", 
        "randomPosition": 0, 
        "name": "Prepare To Eat Out", 
        "image": "goal_goalRestaurant-008f90-000000", 
        "currencyRequested": {
            "cash": 3
        }, 
        "entitiesToMake": "restaurantDateGoal", 
        "goalMenuKey": "restaurantPrepMenu", 
        "timeToDeath": 150
    }, 
    "confesssion_personD": {
        "person": "personD", 
        "name": "confession", 
        "key": "confesssion_personD", 
        "tags": [
            "confesssion"
        ]
    }, 
    "flirtTower_personDConstruction": {
        "key": "flirtTower_personDConstruction", 
        "constructionIdentifier": "flirtTower_personD", 
        "name": "Constructing - Flirt With", 
        "image": "trait-aaaaaa99_tower_towerFlirt", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Flirt With", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "applyTowerConstruction": {
        "key": "applyTowerConstruction", 
        "constructionIdentifier": "applyTower", 
        "name": "Constructing - Be Fueled By", 
        "image": "trait-aaaaaa99_tower_motivation", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Be Fueled By", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "crush": {
        "isaacEntities": [
            "askOutButton"
        ], 
        "buttonKeys": [
            "flirtButton"
        ], 
        "name": "crush", 
        "key": "crush", 
        "aliveEntities": {
            "selfControlGoal": 3
        }
    }, 
    "personBPathMotherland": {
        "startKey": "personB", 
        "endKey": "motherland", 
        "key": "personBPathMotherland"
    }, 
    "memory_dateSad": {
        "image": "trait-013d70", 
        "memoryEmotions": [
            "sadness"
        ], 
        "key": "memory_dateSad", 
        "name": "Date Going Poorly"
    }, 
    "tutorialTextEnergyPanel": {
        "nextEntities": "tutorialIncludeWill", 
        "text": "You need will to build this resource", 
        "yOffset": 190, 
        "entityToMark": "energyMine", 
        "key": "tutorialTextEnergyPanel", 
        "xOffset": 335
    }, 
    "learnFirstMission": {
        "entitiesToMake": [
            "learnFirstMissionGoal"
        ], 
        "key": "learnFirstMission"
    }, 
    "defineSelf": {
        "emotionsToAdd": {
            "anger": 2
        }, 
        "name": "Define Yourself", 
        "image": "tower_memoryDefineSelf-000000", 
        "key": "defineSelf", 
        "memoryKey": "memory_defineSelf", 
        "emotionTags": "self", 
        "resourceKey": "defineSelfMineConstruction", 
        "activeImage": "tower_memoryDefineSelf-000000_activeOutline-0099ff", 
        "description": "I choose my life"
    }, 
    "socialButton": {
        "constructionKey": "socialGoalConstruction", 
        "name": "Socialize", 
        "image": "goal_goalSocial-008f90-000000", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "socialGoal", 
        "key": "socialButton", 
        "description": "Requires social."
    }, 
    "path": {
        "planeOfExistence": "path"
    }, 
    "rentMaker": {
        "image": "disabledTower", 
        "dayCount": 4, 
        "name": "Rent Maker", 
        "key": "rentMaker", 
        "dayStartEntity": "rentGoal"
    }, 
    "personD_ext": {
        "image": [
            "trait-43cce5", 
            "trait-3CB7CE", 
            "trait-49E0FB"
        ], 
        "name": "The One You Admire", 
        "key": "personD_ext", 
        "sourceKey": "personD"
    }, 
    "thinkAboutTowerConstruction": {
        "key": "thinkAboutTowerConstruction", 
        "constructionIdentifier": "thinkAboutTower", 
        "name": "Constructing - Think About", 
        "image": "trait-aaaaaa99_tower_towerThinkAbout", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Think About", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "talkButton_personD": {
        "constructionKey": "talkTower_personDConstruction", 
        "name": "Vent To", 
        "image": "tower_towerTalk", 
        "entityCost": {
            "social_personD": 5
        }, 
        "entityKey": "talkTower_personD", 
        "personHeld": "personD", 
        "managerKey": "personD", 
        "limitingKeys": [
            "talkTower_personD", 
            "talkTower_personDConstruction"
        ], 
        "key": "talkButton_personD", 
        "subPanel": "Defenses", 
        "description": "Uses social to calm all emotions"
    }, 
    "lifeThreatMine": {
        "resourceCount": 3, 
        "name": "Fear for Life", 
        "image": "tower_memoryThreat-000000", 
        "key": "lifeThreatMine", 
        "emotionTags": [
            "self", 
            "fear"
        ], 
        "resourceKeys": [
            "fear"
        ], 
        "activeImage": "tower_memoryThreat-000000_activeOutline-0099ff", 
        "description": "These are serious people - makes fear"
    }, 
    "industryMine": {
        "name": "Work Ethic", 
        "image": "tower_mineIndustrious", 
        "sourceTrait": "motherland", 
        "key": "industryMine", 
        "resourceKeys": [
            "industry"
        ], 
        "activeImage": "tower_mineIndustrious_activeOutline-0099ff"
    }, 
    "learnSpyNewsGoal": {
        "currencyEntities": "consequenceSad", 
        "randomPosition": 1, 
        "name": "Learn The News", 
        "image": "trait-ede9e6_goal_goalLearnSpyNews-000000", 
        "key": "learnSpyNewsGoal", 
        "startEntityKey": "motherland", 
        "currencyRequested": {
            "will": 1
        }, 
        "entitiesToMake": [
            "sinDeposit", 
            "confessionGoal"
        ], 
        "goalMenuKey": "learnSpyNewsMenu", 
        "description": "You cannot ignore the buzz, but what will you find?"
    }, 
    "talkButton_personB": {
        "constructionKey": "talkTower_personBConstruction", 
        "name": "Vent To", 
        "image": "tower_towerTalk", 
        "entityCost": {
            "social_personB": 5
        }, 
        "entityKey": "talkTower_personB", 
        "personHeld": "personB", 
        "managerKey": "personB", 
        "limitingKeys": [
            "talkTower_personB", 
            "talkTower_personBConstruction"
        ], 
        "key": "talkButton_personB", 
        "subPanel": "Defenses", 
        "description": "Uses social to calm all emotions"
    }, 
    "ringExciteMine": {
        "resourceCount": 3, 
        "name": "Excitement", 
        "image": "tower_memoryRing", 
        "key": "ringExciteMine", 
        "emotionTags": [
            "self", 
            "joy"
        ], 
        "resourceKeys": [
            "joy"
        ], 
        "activeImage": "tower_memoryRing_activeOutline-0099ff", 
        "description": "makes joy"
    }, 
    "bombPrep3Goal": {
        "key": "bombPrep3Goal", 
        "randomPosition": 0, 
        "name": "Mission Prep 3", 
        "aliveEntities": "lifeThreat", 
        "image": "trait-ede9e6_goal_goalBombPrep-000000", 
        "makeOnTimeOut": [
            "killedMenu", 
            "gameEnd"
        ], 
        "startEntityKey": "motherland", 
        "currencyRequested": {
            "spy": 5
        }, 
        "description": [
            "Is it too far to turn back? Do I even want to?", 
            [
                "Failing this goal ends the game"
            ]
        ], 
        "entitiesToMake": [
            "missionGoal"
        ], 
        "goalMenuKey": "bombPrep3Menu", 
        "timeToDeath": 600
    }, 
    "findCabJobGoalConstruction": {
        "key": "findCabJobGoalConstruction", 
        "constructionIdentifier": "findCabJobGoal", 
        "name": "Constructing - Become Cabbie", 
        "image": "trait-aaaaaa99_goal_goalCabbie-000000", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Become Cabbie", 
        "killOnTransform": 1, 
        "description": "It takes will to start something new."
    }, 
    "confessionGoal": {
        "key": "confessionGoal", 
        "randomPosition": 0, 
        "name": "Confess", 
        "image": "trait-ede9e6_goal_goalConfession-000000", 
        "startEntityKey": "self", 
        "currencyRequested": {
            "confession": 5
        }, 
        "entitiesToMake": [
            "disavowalButton", 
            "disavowalGoal"
        ], 
        "goalMenuKey": "confessionMenu"
    }, 
    "sadnessMap": {
        "startManagedEntities": [
            "painMine"
        ], 
        "emotionToMap": "sadness", 
        "emotionMappedEntities": [
            "painMineButton", 
            "empathizeButton"
        ], 
        "key": "sadnessMap"
    }, 
    "chapter2Menu": {
        "image": "activeHex", 
        "completeText": "Time to see what this city has to offer", 
        "name": "Chapter 2 - Fitting In", 
        "key": "chapter2Menu"
    }, 
    "daydreamMine_personBConstruction": {
        "key": "daydreamMine_personBConstruction", 
        "constructionIdentifier": "daydreamMine_personB", 
        "name": "Constructing - Daydream", 
        "image": "trait-aaaaaa99_tower_daydream", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Daydream", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "jobQuotaMenu": {
        "image": "trait-ede9e6_goal_goalJobQuota-000000", 
        "completeText": "A bitten apple is better than no apple", 
        "name": "COMPLETED - Job Quota", 
        "key": "jobQuotaMenu"
    }, 
    "tutorialTextWakeUpPanel": {
        "nextEntities": [
            "tutorialTextAdulthood", 
            "tutorialWrongButton"
        ], 
        "text": "You need these to complete this goal", 
        "yOffset": 100, 
        "entityToMark": "wakeGoal", 
        "key": "tutorialTextWakeUpPanel", 
        "xOffset": 435
    }, 
    "memoryStopMenu": {
        "image": "goal_activeHex-000000", 
        "name": "COMPLETED - Keep Memory Alive", 
        "key": "memoryStopMenu"
    }, 
    "confession": {
        "name": "confession", 
        "key": "confession", 
        "tags": [
            "confession"
        ]
    }, 
    "prideJoy": {
        "emotionsToAdd": {
            "joy": 2
        }, 
        "name": "Doing Good", 
        "image": "tower_memoryDateGood", 
        "key": "prideJoy", 
        "memoryKey": "memory_prideJoy", 
        "emotionTags": "self", 
        "resourceKey": "prideJoyMineConstruction", 
        "activeImage": "tower_memoryDateGood_activeOutline-0099ff"
    }, 
    "talkTower_personDConstruction": {
        "key": "talkTower_personDConstruction", 
        "constructionIdentifier": "talkTower_personD", 
        "name": "Constructing - Vent To", 
        "image": "trait-aaaaaa99_tower_towerTalk", 
        "currencyRequested": {
            "social_personD": 5
        }, 
        "constructionName": "Vent To", 
        "killOnTransform": 0
    }, 
    "flashback_joyConstruction": {
        "key": "flashback_joyConstruction", 
        "constructionIdentifier": "flashback_joy", 
        "name": "Flashback", 
        "image": "tower_flashback-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Flashback"
    }, 
    "dateLuckyMineConstruction": {
        "key": "dateLuckyMineConstruction", 
        "constructionIdentifier": "dateLuckyMine", 
        "name": "Feeling Lucky", 
        "image": "tower_memoryRing-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Feeling Lucky"
    }, 
    "bombPrep3Menu": {
        "image": "trait-ede9e6_goal_goalBombPrep-000000", 
        "name": "COMPLETED - Mission Prep 3", 
        "key": "bombPrep3Menu"
    }, 
    "flareMemoryButton": {
        "constructionKey": "flareMemoryGoalConstruction", 
        "name": "Flare", 
        "image": "chess", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "flareMemoryGoal", 
        "key": "flareMemoryButton", 
        "description": "Requires thought."
    }, 
    "cashDeposit": {
        "image": "tower_depositCash", 
        "activeImage": "tower_depositCash_activeOutline-0099ff", 
        "name": "Cash", 
        "key": "cashDeposit", 
        "deposit": {
            "cash": 3
        }
    }, 
    "memory": {
        "mapSpawnKey": "memoryExtension", 
        "maxLevel": 6, 
        "playerKillable": "true", 
        "timeToDeath": 150
    }, 
    "understandAction": {
        "currenciesRequired": [
            "thought"
        ], 
        "currenciesToMake": [
            "understand"
        ], 
        "towerKey": "understandTower", 
        "name": "understand", 
        "key": "understandAction"
    }, 
    "cleanChore": {
        "image": "disabledTower", 
        "dayCount": 4, 
        "name": "Clean Chore", 
        "key": "cleanChore", 
        "dayStartEntity": "cleanChoreGoal"
    }, 
    "bombPrep2Goal": {
        "key": "bombPrep2Goal", 
        "randomPosition": 0, 
        "name": "Mission Prep 2", 
        "aliveEntities": "lifeThreat", 
        "image": "trait-ede9e6_goal_goalBombPrep-000000", 
        "makeOnTimeOut": [
            "killedMenu", 
            "gameEnd"
        ], 
        "startEntityKey": "motherland", 
        "currencyRequested": {
            "travel": 5
        }, 
        "description": [
            "This land has not been your friend. Is this not justified?", 
            [
                "Failing this goal ends the game"
            ]
        ], 
        "entitiesToMake": [
            "bombPrep3Goal"
        ], 
        "goalMenuKey": "bombPrep2Menu", 
        "timeToDeath": 600
    }, 
    "understandMemoryGoal": {
        "key": "understandMemoryGoal", 
        "randomPosition": 0, 
        "name": "Understand", 
        "image": "goal_chess-000000", 
        "killMemory": 1, 
        "currencyRequested": {
            "understand": 3
        }, 
        "goalMenuKey": "understandMemoryMenu"
    }, 
    "fightProgramButton": {
        "constructionKey": "fightProgramGoalConstruction", 
        "name": "Fight Programming", 
        "image": "goal_goalFightProgramming-000000", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "fightProgramGoal", 
        "key": "fightProgramButton", 
        "managerKey": "motherland", 
        "description": "Requires disavowal."
    }, 
    "trait": {
        "playerKillable": "true"
    }, 
    "tutorialDefense": {
        "nextEntities": "tutorialDefenseTab", 
        "text": "Now, look at the adult trait again.", 
        "yOffset": 100, 
        "entityToMark": "adult_ext", 
        "key": "tutorialDefense", 
        "xOffset": 300
    }, 
    "intelligentPathCity": {
        "startKey": "intelligent", 
        "endKey": "city", 
        "key": "intelligentPathCity"
    }, 
    "disavowalTowerConstruction": {
        "key": "disavowalTowerConstruction", 
        "constructionIdentifier": "disavowalTower", 
        "name": "Constructing - Disavow", 
        "image": "trait-aaaaaa99_tower_towerDisavowal", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Disavow", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "adult": {
        "sector": [
            0, 
            0
        ], 
        "name": "Adulthood", 
        "image": "trait-8aed02", 
        "playerKillable": "true", 
        "isaacEntities": [
            "willMineButton", 
            "energyMineButton"
        ], 
        "key": "adult", 
        "startManagedEntities": [
            "willMine", 
            "adultPathSelf"
        ], 
        "description": "You miss the time before this trait"
    }, 
    "impulse": {
        "tags": [
            "impulse"
        ], 
        "name": "impulse", 
        "key": "impulse", 
        "checkDayEnd": 1
    }, 
    "fearFlash": {
        "emotionsToAdd": {
            "fear": 2
        }, 
        "name": "Fear Flash", 
        "image": "tower_memoryFearFlash-000000", 
        "key": "fearFlash", 
        "memoryKey": "memory_fearFlash", 
        "emotionTags": "self", 
        "resourceKey": "fearFlashMineConstruction", 
        "activeImage": "tower_memoryFearFlash-000000_activeOutline-0099ff", 
        "description": "What if I'm found out"
    }, 
    "towerHelpInfoPanel": {
        "components": [
            "towerComponent", 
            "resourceComponent", 
            "constructionComponent"
        ], 
        "helpLines": [
            "These are the activities that you can perform.", 
            "Activating them will generate the appropriate currency and may require a currency of its own."
        ], 
        "name": "Towers", 
        "key": "towerHelpInfoPanel"
    }, 
    "spendTimeMenu": {
        "image": "trait-ede9e6_goal_goalChat-008f90-000000", 
        "name": "COMPLETED - Spend Time With Partner", 
        "key": "spendTimeMenu"
    }, 
    "calmDownMenu": {
        "image": "trait-ede9e6_goal_goalCalmDown-008f90-000000", 
        "completeText": "A little sympathy is a small price to pay for this.", 
        "name": "COMPLETED - Calm Down", 
        "key": "calmDownMenu"
    }, 
    "learnFirstMissionMenu": {
        "image": "trait-ede9e6_goal_goalLearnFirstMission-000000", 
        "name": "COMPLETED - Learn First Mission", 
        "key": "learnFirstMissionMenu"
    }, 
    "confessTower_personBConstruction": {
        "key": "confessTower_personBConstruction", 
        "constructionIdentifier": "confessTower_personB", 
        "name": "Constructing - Confess To", 
        "image": "trait-aaaaaa99_tower_towerConfess", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Confess To", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "currency": {
        "planeOfExistence": "path"
    }, 
    "walkDateMenu": {
        "image": "goal_goalSkate-008f90-000000", 
        "completeText": "That was all you ever wanted.", 
        "name": "COMPLETED - Walking Date", 
        "key": "walkDateMenu"
    }, 
    "work": {
        "name": "work", 
        "key": "work", 
        "tags": [
            "work"
        ]
    }, 
    "driveTower": {
        "image": "tower_drive", 
        "activeImage": "tower_drive_activeOutline-0099ff", 
        "name": "Drive", 
        "key": "driveTower", 
        "description": "Converts desire -> industry"
    }, 
    "sadnessTickModifier": {
        "tickFactor": 0.5, 
        "distractFactor": 0, 
        "image": "modifier-0000ff", 
        "key": "sadnessTickModifier"
    }, 
    "memory_shame": {
        "image": "trait-013d70", 
        "memoryEmotions": [
            "sadness"
        ], 
        "key": "memory_shame", 
        "name": "Shame"
    }, 
    "intelligentPathMotherland": {
        "startKey": "intelligent", 
        "endKey": "motherland", 
        "key": "intelligentPathMotherland"
    }, 
    "spyMenu": {
        "image": "trait-ede9e6_goal_goalSpy-000000", 
        "completeText": [
            "Her actions are a little suspicious,", 
            "but nothing that scary."
        ], 
        "name": "COMPLETED - Spy", 
        "key": "spyMenu"
    }, 
    "fearFlashMineConstruction": {
        "key": "fearFlashMineConstruction", 
        "constructionIdentifier": "fearFlashMine", 
        "name": "Fear Flash", 
        "image": "tower_memoryFearFlash-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Fear Flash"
    }, 
    "spy": {
        "name": "spy", 
        "key": "spy", 
        "tags": [
            "spy"
        ]
    }, 
    "cleanAction": {
        "currenciesRequired": [
            "chore", 
            "industry"
        ], 
        "currenciesToMake": [
            "work"
        ], 
        "towerKey": "cleanTower", 
        "name": "Work", 
        "key": "cleanAction"
    }, 
    "chapter3Menu": {
        "image": "activeHex", 
        "name": "Chapter 3 - It Begins", 
        "key": "chapter3Menu"
    }, 
    "personB_ext": {
        "image": [
            "trait-68cccc", 
            "trait-5DB7B7", 
            "trait-72E0E0"
        ], 
        "name": "The Pretty Person", 
        "key": "personB_ext", 
        "sourceKey": "personB"
    }, 
    "programming": {
        "emotionsToAdd": {
            "fear": 2
        }, 
        "name": "Programming", 
        "image": "tower_memoryProgramming-000000", 
        "key": "programming", 
        "memoryKey": "memory_programming", 
        "emotionTags": "self", 
        "resourceKey": "programmingMineConstruction", 
        "activeImage": "tower_memoryProgramming-000000_activeOutline-0099ff", 
        "description": "You must obey"
    }, 
    "impressMineButton_personD": {
        "constructionKey": "impressMine_personDConstruction", 
        "name": "Desire To Impress", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_mineImpress", 
        "currenciesMade": [
            "desire"
        ], 
        "entityKey": "impressMine_personD", 
        "limitingKeys": [
            "impressMine_personD", 
            "impressMine_personDConstruction"
        ], 
        "key": "impressMineButton_personD", 
        "managerKey": "personD", 
        "description": "makes desire"
    }, 
    "hundredHealthComponent": {
        "health": 100
    }, 
    "agent": {
        "entitiesToMake": [
            "contactButton", 
            "spyButton"
        ], 
        "key": "agent"
    }, 
    "threatMineConstruction": {
        "key": "threatMineConstruction", 
        "constructionIdentifier": "threatMine", 
        "name": "Threat", 
        "image": "tower_memoryThreat-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Threat"
    }, 
    "contact_personB": {
        "person": "personB", 
        "name": "contact", 
        "key": "contact_personB", 
        "tags": [
            "contact"
        ]
    }, 
    "caution": {
        "name": "caution", 
        "key": "caution", 
        "tags": [
            "caution"
        ]
    }, 
    "homeThoughtMine": {
        "name": "Thoughts of Home", 
        "image": "tower_mineThoughtsOfHome", 
        "sourceTrait": "motherland", 
        "key": "homeThoughtMine", 
        "resourceKeys": [
            "energy"
        ], 
        "activeImage": "tower_mineThoughtsOfHome_activeOutline-0099ff"
    }, 
    "contact_personD": {
        "person": "personD", 
        "name": "contact", 
        "key": "contact_personD", 
        "tags": [
            "contact"
        ]
    }, 
    "contactAction_personB": {
        "currenciesRequired": [
            "social_personB"
        ], 
        "currenciesToMake": [
            "contact_personB"
        ], 
        "towerKey": "contactTower_personB", 
        "name": "contact - The Pretty Person", 
        "key": "contactAction_personB"
    }, 
    "walkButton": {
        "constructionKey": "walkTowerConstruction", 
        "name": "Walk", 
        "image": "tower_towerWalk", 
        "entityCost": {
            "energy": 5
        }, 
        "entityKey": "walkTower", 
        "limitingKeys": [
            "walkTower", 
            "walkTowerConstruction"
        ], 
        "key": "walkButton", 
        "subPanel": "Defenses", 
        "description": "Uses energy to calm all emotions"
    }, 
    "contactAction_personD": {
        "currenciesRequired": [
            "social_personD"
        ], 
        "currenciesToMake": [
            "contact_personD"
        ], 
        "towerKey": "contactTower_personD", 
        "name": "contact - The One You Admire", 
        "key": "contactAction_personD"
    }, 
    "daydreamMine_personDConstruction": {
        "key": "daydreamMine_personDConstruction", 
        "constructionIdentifier": "daydreamMine_personD", 
        "name": "Constructing - Daydream", 
        "image": "trait-aaaaaa99_tower_daydream", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Daydream", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "sinDeposit": {
        "name": "Sin", 
        "aliveEntities": "shame", 
        "image": "tower_trait-ede9e6_depositSin", 
        "deposit": {
            "sin": 5
        }, 
        "key": "sinDeposit", 
        "activeImage": "tower_trait-ede9e6_depositSin_activeOutline-0099ff"
    }, 
    "travel": {
        "name": "travel", 
        "key": "travel", 
        "tags": [
            "travel"
        ]
    }, 
    "cabCompete": {
        "entitiesToKill": [
            "cabPride"
        ], 
        "goalKey": "cabAction", 
        "startEntityKey": "city", 
        "name": "Promotion", 
        "key": "cabCompete"
    }, 
    "flirt_personD": {
        "person": "personD", 
        "name": "flirt", 
        "key": "flirt_personD", 
        "tags": [
            "flirt"
        ]
    }, 
    "makeAgentE": {
        "opinionKey": "knownAgent", 
        "personKey": "agentE", 
        "key": "makeAgentE"
    }, 
    "flirtTower_personBConstruction": {
        "key": "flirtTower_personBConstruction", 
        "constructionIdentifier": "flirtTower_personB", 
        "name": "Constructing - Flirt With", 
        "image": "trait-aaaaaa99_tower_towerFlirt", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Flirt With", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "intelligent": {
        "sector": [
            1, 
            1
        ], 
        "name": "Intelligent", 
        "image": "trait-ffd700", 
        "isaacEntities": [
            "thoughtMineButton", 
            "chessButton", 
            "thinkAboutButton", 
            "driveButton"
        ], 
        "key": "intelligent", 
        "startManagedEntities": [
            "intelligentPathCity", 
            "intelligentPathMotherland"
        ], 
        "description": "The trait you are most proud of"
    }, 
    "thinkAboutAction": {
        "currenciesRequired": [
            "thought"
        ], 
        "currenciesToMake": [
            "sympathy"
        ], 
        "towerKey": "thinkAboutTower", 
        "name": "think about", 
        "key": "thinkAboutAction"
    }, 
    "tutorialUnpauseBuild": {
        "text": [
            "And then unpause the game."
        ], 
        "nextEntities": "tutorialWaitForEnergyMine", 
        "key": "tutorialUnpauseBuild", 
        "yStart": 650
    }, 
    "memory_disavowSadness": {
        "image": "trait-013d70", 
        "memoryEmotions": [
            "sadness"
        ], 
        "key": "memory_disavowSadness", 
        "name": "Alienation"
    }, 
    "chapter6Menu": {
        "image": "activeHex", 
        "name": "Chapter 6 - Zenith", 
        "key": "chapter6Menu"
    }, 
    "insecure": {
        "name": "insecure", 
        "key": "insecure", 
        "aliveEntities": {
            "supportGoal": 3
        }
    }, 
    "fightProgramMenu": {
        "image": "goal_goalFightProgramming-000000", 
        "completeText": "Under no control but your own.", 
        "name": "COMPLETED - Fight Programming", 
        "key": "fightProgramMenu"
    }, 
    "workShiftGoal": {
        "currencyEntities": {
            "nastyComment": 50
        }, 
        "randomPosition": 0, 
        "name": "Work Shift", 
        "image": "goal_goalJobQuota-000000", 
        "key": "workShiftGoal", 
        "startEntityKey": "city", 
        "currencyRequested": {
            "work": 3
        }, 
        "entitiesToMake": [
            "cashDeposit"
        ], 
        "goalMenuKey": "workShiftMenu"
    }, 
    "parkPrepMenu": {
        "image": "goal_goalPark-008f90-000000", 
        "name": "COMPLETED - Prepare For Park Date", 
        "key": "parkPrepMenu"
    }, 
    "threatMine": {
        "resourceCount": 1, 
        "name": "Threat", 
        "image": "tower_memoryThreat-000000", 
        "key": "threatMine", 
        "emotionTags": [
            "self", 
            "fear"
        ], 
        "resourceKeys": [
            "fear"
        ], 
        "activeImage": "tower_memoryThreat-000000_activeOutline-0099ff", 
        "description": "makes fear"
    }, 
    "learnSpyNewsMenu": {
        "image": "trait-ede9e6_goal_goalLearnSpyNews-000000", 
        "completeText": "It was not your hand that killed the teacher but it was your tongue.", 
        "name": "COMPLETED - Learn The News", 
        "key": "learnSpyNewsMenu"
    }, 
    "fearMap": {
        "startManagedEntities": [
            "cautionMine"
        ], 
        "emotionToMap": "fear", 
        "emotionMappedEntities": [
            "cautionMineButton", 
            "prepareButton"
        ], 
        "key": "fearMap"
    }, 
    "bombPrep1Menu": {
        "image": "trait-ede9e6_goal_goalBombPrep-000000", 
        "name": "COMPLETED - Mission Prep 1", 
        "key": "bombPrep1Menu"
    }, 
    "learnSad": {
        "emotionsToAdd": {
            "sadness": 2
        }, 
        "name": "Learnings", 
        "image": "tower_memoryLearn-000000", 
        "key": "learnSad", 
        "memoryKey": "memory_learnSad", 
        "emotionTags": "self", 
        "resourceKey": "learnSadMineConstruction", 
        "activeImage": "tower_memoryLearn-000000_activeOutline-0099ff", 
        "description": "Other people's wounds can hurt too"
    }, 
    "breakdownMenu": {
        "endGame": 1, 
        "completeText": [
            "This life proved too stressful for you to take. It's time to try", 
            "another."
        ], 
        "key": "breakdownMenu"
    }, 
    "betrayalMineConstruction": {
        "key": "betrayalMineConstruction", 
        "constructionIdentifier": "betrayalMine", 
        "name": "Betrayal", 
        "image": "tower_memoryDefineSelf-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Betrayal"
    }, 
    "angerMap": {
        "startManagedEntities": [
            "frustrationMine"
        ], 
        "emotionToMap": "anger", 
        "emotionMappedEntities": [
            "frustrationMineButton", 
            "applyButton"
        ], 
        "key": "angerMap"
    }, 
    "flirt_personB": {
        "person": "personB", 
        "name": "flirt", 
        "key": "flirt_personB", 
        "tags": [
            "flirt"
        ]
    }, 
    "memory_missHome": {
        "image": "trait-013d70", 
        "memoryEmotions": [
            "sadness"
        ], 
        "key": "memory_missHome", 
        "name": "Missing Home"
    }, 
    "tutorialBuildPaint": {
        "text": "And build a paint tower", 
        "yOffset": 260, 
        "xOffset": 710, 
        "key": "tutorialBuildPaint", 
        "entityToMark": "adult"
    }, 
    "fantasyMineButton_personB": {
        "constructionKey": "fantasyMine_personBConstruction", 
        "name": "Fantasize About", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_mineFantasy", 
        "currenciesMade": [
            "energy"
        ], 
        "entityKey": "fantasyMine_personB", 
        "limitingKeys": [
            "fantasyMine_personB", 
            "fantasyMine_personBConstruction"
        ], 
        "key": "fantasyMineButton_personB", 
        "managerKey": "personB", 
        "description": "makes energy"
    }, 
    "cabChoreStartCheck": {
        "count": 2, 
        "entitiesToMake": "cabChore", 
        "notificationChecked": "goalComplete", 
        "notificationArgs": {
            "key": "cabAction"
        }, 
        "key": "cabChoreStartCheck"
    }, 
    "rentGoal": {
        "key": "rentGoal", 
        "randomPosition": 0, 
        "name": "Pay Rent", 
        "aliveEntities": "threat", 
        "image": "trait-ede9e6_goal_goalPayRent-000000", 
        "startEntityKey": "adult", 
        "currencyRequested": {
            "cash": 2
        }, 
        "goalMenuKey": "rentMenu", 
        "description": "You don't want any trouble."
    }, 
    "prepareTowerConstruction": {
        "key": "prepareTowerConstruction", 
        "constructionIdentifier": "prepareTower", 
        "name": "Constructing - Prepare", 
        "image": "trait-aaaaaa99_tower_spy", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Prepare", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "contactButton_personD": {
        "constructionKey": "contactTower_personDConstruction", 
        "name": "Contact", 
        "image": "tower_towerContact", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "contactTower_personD", 
        "personHeld": "personD", 
        "managerKey": "personD", 
        "limitingKeys": [
            "contactTower_personD", 
            "contactTower_personDConstruction"
        ], 
        "key": "contactButton_personD", 
        "subPanel": "Towers", 
        "description": "Converts social -> contact"
    }, 
    "contactButton_personB": {
        "constructionKey": "contactTower_personBConstruction", 
        "name": "Contact", 
        "image": "tower_towerContact", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "contactTower_personB", 
        "personHeld": "personB", 
        "managerKey": "personB", 
        "limitingKeys": [
            "contactTower_personB", 
            "contactTower_personBConstruction"
        ], 
        "key": "contactButton_personB", 
        "subPanel": "Towers", 
        "description": "Converts social -> contact"
    }, 
    "angerTickModifier": {
        "tickFactor": 1.5, 
        "distractFactor": 0, 
        "image": "modifier-ff0000", 
        "timeToDeath": 15, 
        "key": "angerTickModifier"
    }, 
    "prideJoyMine": {
        "resourceCount": 2, 
        "name": "Doing Good", 
        "image": "tower_memoryDateGood", 
        "key": "prideJoyMine", 
        "emotionTags": [
            "self", 
            "joy"
        ], 
        "resourceKeys": [
            "joy"
        ], 
        "activeImage": "tower_memoryDateGood_activeOutline-0099ff", 
        "description": "makes joy"
    }, 
    "dateAction_personD": {
        "currenciesRequired": [
            "flirt_personD", 
            "travel"
        ], 
        "currenciesToMake": [
            "date_personD"
        ], 
        "towerKey": "dateTower_personD", 
        "name": "date - The One You Admire", 
        "key": "dateAction_personD"
    }, 
    "angerResourceModifier": {
        "doubleResources": [
            "impressMine", 
            "industryMine"
        ], 
        "stopResources": [
            "thoughtMine", 
            "cautionMine"
        ], 
        "image": "modifier-ff0000", 
        "key": "angerResourceModifier"
    }, 
    "dateAction_personB": {
        "currenciesRequired": [
            "flirt_personB", 
            "travel"
        ], 
        "currenciesToMake": [
            "date_personB"
        ], 
        "towerKey": "dateTower_personB", 
        "name": "date - The Pretty Person", 
        "key": "dateAction_personB"
    }, 
    "nastyCommentMineConstruction": {
        "key": "nastyCommentMineConstruction", 
        "constructionIdentifier": "nastyCommentMine", 
        "name": "Casual Racism", 
        "image": "tower_memoryNastyComment-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Casual Racism"
    }, 
    "jobQuotaGoal": {
        "key": "jobQuotaGoal", 
        "killOnTimeOut": [
            "cleanButton", 
            "cabButton", 
            "job", 
            "cleanTower", 
            "cabTower", 
            "workShiftButton"
        ], 
        "randomPosition": 1, 
        "name": "Job Quota", 
        "makeOnTimeOut": [
            "findJob", 
            "loseJobMenu"
        ], 
        "image": "trait-ede9e6_goal_goalJobQuota-000000", 
        "startEntityKey": "city", 
        "currencyRequested": {
            "cash": 1
        }, 
        "goalMenuKey": "jobQuotaMenu", 
        "timeToDeath": 240
    }, 
    "resourceModifier": {
        "timeToDeath": 15
    }, 
    "socialGoal": {
        "key": "socialGoal", 
        "randomPosition": 0, 
        "name": "Socialize", 
        "image": "goal_goalSocial-008f90-000000", 
        "currencyRequested": {
            "social": 5
        }, 
        "goalMenuKey": "socialMenu"
    }, 
    "gainControl_fear": {
        "barColor": 990099, 
        "text": "Panic has overwhelmed you.", 
        "textColor": 660066, 
        "key": "gainControl_fear"
    }, 
    "buyRingGoalConstruction": {
        "key": "buyRingGoalConstruction", 
        "constructionIdentifier": "buyRingGoal", 
        "name": "Constructing - Buy Ring", 
        "image": "trait-aaaaaa99_goal_goalBuyRing-008f90-000000", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Buy Ring", 
        "killOnTransform": 1, 
        "description": "It takes will to start something new."
    }, 
    "contactAction": {
        "currenciesRequired": [
            "travel"
        ], 
        "currenciesToMake": [
            "contact"
        ], 
        "towerKey": "contactTower", 
        "name": "Contact", 
        "key": "contactAction"
    }, 
    "tutorialFinishTrain": {
        "nextEntities": "tutorialUnpauseBuild", 
        "text": "And then the self to complete the train's path", 
        "yOffset": 100, 
        "entityToMark": "self", 
        "key": "tutorialFinishTrain", 
        "xOffset": 400
    }, 
    "homeThoughtMineButton": {
        "constructionKey": "homeThoughtMineConstruction", 
        "name": "Thoughts of Home", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_mineThoughtsOfHome", 
        "currenciesMade": [
            "energy"
        ], 
        "entityKey": "homeThoughtMine", 
        "limitingKeys": [
            "homeThoughtMine", 
            "homeThoughtMineConstruction"
        ], 
        "key": "homeThoughtMineButton", 
        "managerKey": "motherland", 
        "description": "makes energy"
    }, 
    "selfControlGoal": {
        "key": "selfControlGoal", 
        "randomPosition": 1, 
        "name": "Don't Say Stupid Thing", 
        "makeOnTimeOut": "shame", 
        "image": "trait-ede9e6_goal_goalChat-008f90-000000", 
        "currencyRequested": {
            "will": 1
        }, 
        "goalMenuKey": "selfControlMenu", 
        "timeToDeath": 150
    }, 
    "spendTimeGoal": {
        "key": "spendTimeGoal", 
        "randomPosition": 1, 
        "name": "Spend Time With Partner", 
        "makeOnTimeOut": "shame", 
        "image": "trait-ede9e6_goal_goalChat-008f90-000000", 
        "currencyRequested": {
            "date": 3
        }, 
        "goalMenuKey": "spendTimeMenu", 
        "timeToDeath": 150
    }, 
    "disavowAngerMineConstruction": {
        "key": "disavowAngerMineConstruction", 
        "constructionIdentifier": "disavowAngerMine", 
        "name": "Alienation", 
        "image": "tower_memoryDefineSelf-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Alienation"
    }, 
    "defineSelfMine": {
        "resourceCount": 2, 
        "name": "Define Yourself", 
        "image": "tower_memoryDefineSelf-000000", 
        "key": "defineSelfMine", 
        "emotionTags": [
            "self", 
            "anger"
        ], 
        "resourceKeys": [
            "anger"
        ], 
        "activeImage": "tower_memoryDefineSelf-000000_activeOutline-0099ff", 
        "description": "I choose my life - makes anger"
    }, 
    "disavowAnger": {
        "emotionsToAdd": {
            "anger": 3
        }, 
        "name": "Alienation", 
        "image": "tower_memoryDefineSelf-000000", 
        "key": "disavowAnger", 
        "memoryKey": "memory_disavowAnger", 
        "emotionTags": "self", 
        "resourceKey": "disavowAngerMineConstruction", 
        "activeImage": "tower_memoryDefineSelf-000000_activeOutline-0099ff"
    }, 
    "frustrationMine": {
        "image": "tower_mineFrustration", 
        "resourceKeys": [
            "frustration"
        ], 
        "name": "Frustration Mine", 
        "key": "frustrationMine", 
        "activeImage": "tower_mineFrustration_activeOutline-0099ff"
    }, 
    "confessButton_personB": {
        "constructionKey": "confessTower_personBConstruction", 
        "name": "Confess To", 
        "image": "tower_towerConfess", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "confessTower_personB", 
        "personHeld": "personB", 
        "managerKey": "personB", 
        "limitingKeys": [
            "confessTower_personB", 
            "confessTower_personBConstruction"
        ], 
        "key": "confessButton_personB", 
        "subPanel": "Towers", 
        "description": "Converts sin -> confession"
    }, 
    "gameStateEntity": {
        "name": "gameStateEntity", 
        "key": "gameStateEntity"
    }, 
    "prepareButton": {
        "constructionKey": "prepareTowerConstruction", 
        "name": "Prepare", 
        "image": "tower_spy", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "prepareTower", 
        "limitingKeys": [
            "prepareTower", 
            "prepareTowerConstruction"
        ], 
        "key": "prepareButton", 
        "description": "Converts caution -> preparation"
    }, 
    "memory_learnSad": {
        "image": "trait-013d70", 
        "memoryEmotions": [
            "sadness"
        ], 
        "key": "memory_learnSad", 
        "name": "Learnings"
    }, 
    "tutorialTextWakeUpPanel2": {
        "text": "Close this panel here", 
        "yOffset": 270, 
        "xOffset": 435, 
        "key": "tutorialTextWakeUpPanel2", 
        "entityToMark": "wakeGoal"
    }, 
    "proposeGoal": {
        "key": "proposeGoal", 
        "randomPosition": 0, 
        "name": "Propose", 
        "image": "goal_goalPropose-008f90-000000", 
        "opinionToAdd": "married", 
        "currencyRequested": {
            "date": 5
        }, 
        "goalMenuKey": "proposeMenu"
    }, 
    "programmingMine": {
        "resourceCount": 2, 
        "name": "Programming", 
        "image": "tower_memoryProgramming-000000", 
        "key": "programmingMine", 
        "emotionTags": [
            "self", 
            "fear"
        ], 
        "resourceKeys": [
            "fear"
        ], 
        "activeImage": "tower_memoryProgramming-000000_activeOutline-0099ff", 
        "description": "You must obey - makes fear"
    }, 
    "sin": {
        "persists": 1, 
        "name": "sin", 
        "key": "sin", 
        "tags": [
            "sin"
        ]
    }, 
    "paintAction": {
        "currenciesRequired": [
            "energy"
        ], 
        "towerKey": "paintTower", 
        "name": "paint", 
        "key": "paintAction"
    }, 
    "planTowerConstruction": {
        "key": "planTowerConstruction", 
        "constructionIdentifier": "planTower", 
        "name": "Constructing - Plan", 
        "image": "trait-aaaaaa99_tower_towerPlan", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Plan", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "buyRingMenu": {
        "image": "goal_goalBuyRing-008f90-000000", 
        "name": "COMPLETED - Buy Ring", 
        "key": "buyRingMenu"
    }, 
    "cleanPrideCheck": {
        "count": 3, 
        "entitiesToMake": "cleanPride", 
        "notificationChecked": "goalComplete", 
        "notificationArgs": {
            "key": "cleanAction"
        }, 
        "key": "cleanPrideCheck"
    }, 
    "socialMine_personDConstruction": {
        "key": "socialMine_personDConstruction", 
        "constructionIdentifier": "socialMine_personD", 
        "name": "Constructing - Spend Time With", 
        "image": "trait-aaaaaa99_tower_mineSocial", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Spend Time With", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "bombPrep2Menu": {
        "image": "trait-ede9e6_goal_goalBombPrep-000000", 
        "name": "COMPLETED - Mission Prep 2", 
        "key": "bombPrep2Menu"
    }, 
    "singleUseTrainPath": {
        "focusCost": 0, 
        "requiresFullPath": "false"
    }, 
    "flareMemoryGoalConstruction": {
        "key": "flareMemoryGoalConstruction", 
        "constructionIdentifier": "flareMemoryGoal", 
        "name": "Constructing - Flare", 
        "image": "trait-aaaaaa99_chess", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Flare", 
        "killOnTransform": 1, 
        "description": "It takes will to start something new."
    }, 
    "adultPathSelf": {
        "startKey": "adult", 
        "endKey": "self", 
        "key": "adultPathSelf"
    }, 
    "missionGoal": {
        "key": "missionGoal", 
        "randomPosition": 0, 
        "name": "Mission", 
        "aliveEntities": "lifeThreat", 
        "image": "trait-ede9e6_goal_goalMission-000000", 
        "makeOnTimeOut": [
            "killedMenu", 
            "gameEnd"
        ], 
        "startEntityKey": "motherland", 
        "currencyRequested": {
            "will": 5
        }, 
        "description": [
            "This is it.", 
            [
                "Failing this goal ends the game"
            ]
        ], 
        "entitiesToMake": [
            "gameEnd"
        ], 
        "goalMenuKey": "missionMenu", 
        "timeToDeath": 600
    }, 
    "killedMenu": {
        "endGame": 1, 
        "completeText": [
            "This was too important for them to take risks.", 
            "Your delays worried people, and when these people worry, you die."
        ], 
        "key": "killedMenu"
    }, 
    "energy": {
        "name": "energy", 
        "key": "energy", 
        "tags": [
            "energy"
        ]
    }, 
    "consequenceSad": {
        "emotionsToAdd": {
            "sadness": 2
        }, 
        "name": "Consequences", 
        "image": "tower_memoryConsequence-000000", 
        "key": "consequenceSad", 
        "memoryKey": "memory_consequenceSad", 
        "emotionTags": "self", 
        "resourceKey": "consequenceSadMineConstruction", 
        "activeImage": "tower_memoryConsequence-000000_activeOutline-0099ff", 
        "description": "You did not know"
    }, 
    "empathizeAction": {
        "currenciesRequired": [
            "pain"
        ], 
        "currenciesToMake": [
            "sympathy"
        ], 
        "towerKey": "empathizeTower", 
        "name": "Empathize", 
        "key": "empathizeAction"
    }, 
    "rechargeAction": {
        "currenciesRequired": [
            "cheer"
        ], 
        "currenciesToMake": [
            "energy"
        ], 
        "towerKey": "rechargeTower", 
        "name": "Recharge", 
        "key": "rechargeAction"
    }, 
    "confessAction_personB": {
        "towerKey": "confessTower_personB", 
        "name": "confess - The Pretty Person", 
        "entitiesOnConvert": "confessFear", 
        "currenciesRequired": [
            "sin"
        ], 
        "currenciesToMake": [
            "confession"
        ], 
        "key": "confessAction_personB"
    }, 
    "driveButton": {
        "constructionKey": "driveTowerConstruction", 
        "maxTowerCount": 1, 
        "name": "Drive", 
        "image": "tower_drive", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "driveTower", 
        "limitingKeys": [
            "driveTower", 
            "driveTowerConstruction"
        ], 
        "key": "driveButton", 
        "subPanel": "Towers", 
        "description": "Converts desire -> industry"
    }, 
    "cabChoreGoal": {
        "key": "cabChoreGoal", 
        "killOnTimeOut": [
            "cabButton", 
            "job", 
            "cabTower"
        ], 
        "randomPosition": 1, 
        "name": "Favor for Boss", 
        "makeOnTimeOut": "findJob", 
        "image": "trait-ede9e6_goal_goalCabChore-000000", 
        "description": "Favor. Unpaid labor. Semantics.", 
        "startEntityKey": "city", 
        "currencyRequested": {
            "travel": 2
        }, 
        "goalMenuKey": "cabChoreMenu", 
        "timeToDeath": 120
    }, 
    "planEscapeGoal": {
        "key": "planEscapeGoal", 
        "randomPosition": 0, 
        "name": "Plan Escape", 
        "image": "trait-ede9e6_goal_goalEscape-000000", 
        "currencyRequested": {
            "preparation": 5
        }, 
        "entitiesToMake": [
            "buyEscapeGoal"
        ], 
        "goalMenuKey": "planEscapeMenu"
    }, 
    "memory_lifeThreat": {
        "image": "trait-7533f2", 
        "memoryEmotions": [
            "fear"
        ], 
        "key": "memory_lifeThreat", 
        "name": "Fear for Life"
    }, 
    "disavowAngerMine": {
        "resourceCount": 3, 
        "name": "Alienation", 
        "image": "tower_memoryDefineSelf-000000", 
        "key": "disavowAngerMine", 
        "emotionTags": [
            "self", 
            "anger"
        ], 
        "resourceKeys": [
            "anger"
        ], 
        "activeImage": "tower_memoryDefineSelf-000000_activeOutline-0099ff", 
        "description": "makes anger"
    }, 
    "fearFlashMine": {
        "resourceCount": 2, 
        "name": "Fear Flash", 
        "image": "tower_memoryFearFlash-000000", 
        "key": "fearFlashMine", 
        "emotionTags": [
            "self", 
            "fear"
        ], 
        "resourceKeys": [
            "fear"
        ], 
        "activeImage": "tower_memoryFearFlash-000000_activeOutline-0099ff", 
        "description": "What if I'm found out - makes fear"
    }, 
    "dateGreat": {
        "emotionsToAdd": {
            "joy": 3
        }, 
        "name": "Date Was Great", 
        "image": "tower_memoryDateGood", 
        "key": "dateGreat", 
        "memoryKey": "memory_dateGreat", 
        "emotionTags": "self", 
        "resourceKey": "dateGreatMineConstruction", 
        "activeImage": "tower_memoryDateGood_activeOutline-0099ff"
    }, 
    "impressMine_personB": {
        "name": "Desire To Impress", 
        "image": "tower_mineImpress", 
        "sourceTrait": "personB", 
        "key": "impressMine_personB", 
        "resourceKeys": [
            "desire"
        ], 
        "activeImage": "tower_mineImpress_activeOutline-0099ff"
    }, 
    "impressMine_personD": {
        "name": "Desire To Impress", 
        "image": "tower_mineImpress", 
        "sourceTrait": "personD", 
        "key": "impressMine_personD", 
        "resourceKeys": [
            "desire"
        ], 
        "activeImage": "tower_mineImpress_activeOutline-0099ff"
    }, 
    "nastyComment": {
        "emotionsToAdd": {
            "anger": 2
        }, 
        "name": "Casual Racism", 
        "image": "tower_memoryNastyComment-000000", 
        "key": "nastyComment", 
        "memoryKey": "memory_nastyComment", 
        "emotionTags": "self", 
        "resourceKey": "nastyCommentMineConstruction", 
        "activeImage": "tower_memoryNastyComment-000000_activeOutline-0099ff"
    }, 
    "choreButton": {
        "constructionKey": "choreTowerConstruction", 
        "name": "Do Chores", 
        "image": "tower_towerChores", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "choreTower", 
        "subPanel": "Towers", 
        "limitingKeys": [
            "choreTower", 
            "choreTowerConstruction"
        ], 
        "key": "choreButton", 
        "managerKey": "adult", 
        "description": "Converts energy -> chore"
    }, 
    "tutorialTextEnergy": {
        "nextEntities": "tutorialFinishGoalTrain", 
        "text": "Now connect to the mine", 
        "xOffset": 200, 
        "key": "tutorialTextEnergy", 
        "entityToMark": "energyMine"
    }, 
    "meetPersonButton": {
        "constructionKey": "meetPersonGoalConstruction", 
        "name": "Meet New Person", 
        "image": "goal_goalMeetNewPerson-000000", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "meetPersonGoal", 
        "key": "meetPersonButton", 
        "managerKey": "city", 
        "description": "Requires travel."
    }, 
    "empathizeTower": {
        "image": "tower_social", 
        "activeImage": "tower_social_activeOutline-0099ff", 
        "name": "Empathize", 
        "key": "empathizeTower", 
        "description": "Converts pain -> sympathy"
    }, 
    "meetPersonGoal": {
        "randomPosition": 0, 
        "name": "Meet New Person", 
        "currencyRequested": {
            "travel": 3
        }, 
        "image": "goal_goalMeetNewPerson-000000", 
        "minSelfDist": 4, 
        "startEntityKey": "self", 
        "key": "meetPersonGoal", 
        "goalMenuKey": "meetPersonMenu"
    }, 
    "rechargeTower": {
        "image": "tower_sun", 
        "activeImage": "tower_sun_activeOutline-0099ff", 
        "name": "Recharge", 
        "key": "rechargeTower", 
        "description": "Converts cheer -> energy"
    }, 
    "dateLuckyMine": {
        "resourceCount": 3, 
        "name": "Feeling Lucky", 
        "image": "tower_memoryRing", 
        "key": "dateLuckyMine", 
        "emotionTags": [
            "self", 
            "joy"
        ], 
        "resourceKeys": [
            "joy"
        ], 
        "activeImage": "tower_memoryRing_activeOutline-0099ff", 
        "description": "How did you get this lucky? - makes joy"
    }, 
    "disavowSadness": {
        "emotionsToAdd": {
            "sadness": 3
        }, 
        "name": "Alienation", 
        "image": "tower_memoryMissHome-000000", 
        "key": "disavowSadness", 
        "memoryKey": "memory_disavowSadness", 
        "emotionTags": "self", 
        "resourceKey": "disavowSadnessMineConstruction", 
        "activeImage": "tower_memoryMissHome-000000_activeOutline-0099ff"
    }, 
    "chessAction": {
        "currenciesRequired": [
            "thought"
        ], 
        "towerKey": "chessTower", 
        "name": "chess", 
        "key": "chessAction"
    }, 
    "towerFocusPathStopComponent": {
        "particleKey": "starParticleEffect"
    }, 
    "shameMine": {
        "resourceCount": 3, 
        "name": "Shame", 
        "image": "tower_memoryShame-000000", 
        "key": "shameMine", 
        "emotionTags": [
            "self", 
            "sadness"
        ], 
        "resourceKeys": [
            "sadness"
        ], 
        "activeImage": "tower_memoryShame-000000_activeOutline-0099ff", 
        "description": "makes sadness"
    }, 
    "learnAboutMenu": {
        "image": "trait-ede9e6_goal_goalLearnAbout-008f90-000000", 
        "completeText": [
            "We all have our demons.", 
            "It helps to see them shared"
        ], 
        "name": "COMPLETED - Learn About", 
        "key": "learnAboutMenu"
    }, 
    "hasProject": {
        "successOpinions": {
            "needsSupport": 60
        }, 
        "name": "has project", 
        "key": "hasProject", 
        "aliveEntities": {
            "disablePersonD": 3
        }
    }, 
    "threat": {
        "emotionsToAdd": {
            "fear": 1
        }, 
        "name": "Threat", 
        "image": "tower_memoryThreat-000000", 
        "key": "threat", 
        "memoryKey": "memory_threat", 
        "emotionTags": "self", 
        "resourceKey": "threatMineConstruction", 
        "activeImage": "tower_memoryThreat-000000_activeOutline-0099ff"
    }, 
    "adult_ext": {
        "image": [
            "trait-8aed02", 
            "trait-7CD501", 
            "trait-97FF02"
        ], 
        "name": "Adulthood", 
        "key": "adult_ext", 
        "sourceKey": "adult"
    }, 
    "papersMenu": {
        "image": "trait-ede9e6_goal_goalDeliverPapers-000000", 
        "completeText": [
            "Many people would have opened them.", 
            "You were too scared"
        ], 
        "name": "COMPLETED - Deliver Papers", 
        "key": "papersMenu"
    }, 
    "planEscapeMenu": {
        "image": "trait-ede9e6_goal_goalEscape-000000", 
        "name": "COMPLETED - Plan Escape", 
        "key": "planEscapeMenu"
    }, 
    "choreTower": {
        "image": "tower_towerChores", 
        "activeImage": "tower_towerChores_activeOutline-0099ff", 
        "name": "Do Chores", 
        "key": "choreTower", 
        "description": "Converts energy -> chore"
    }, 
    "flirtAction_personB": {
        "currenciesRequired": [
            "social_personB"
        ], 
        "currenciesToMake": [
            "flirt_personB"
        ], 
        "towerKey": "flirtTower_personB", 
        "name": "flirt - The Pretty Person", 
        "key": "flirtAction_personB"
    }, 
    "flashback_sadness": {
        "resourceCount": 1, 
        "name": "Flashback", 
        "image": "tower_flashback-000000", 
        "key": "flashback_sadness", 
        "emotionTags": "sadness", 
        "resourceKeys": [
            "sadness"
        ], 
        "activeImage": "tower_flashback-000000_activeOutline-0099ff", 
        "description": "makes sadness"
    }, 
    "sympathy": {
        "name": "sympathy", 
        "key": "sympathy", 
        "tags": [
            "sympathy"
        ]
    }, 
    "personBPathCity": {
        "startKey": "personB", 
        "endKey": "city", 
        "key": "personBPathCity"
    }, 
    "memory_betrayal": {
        "image": "trait-aa1717", 
        "memoryEmotions": [
            "anger"
        ], 
        "key": "memory_betrayal", 
        "name": "Betrayal"
    }, 
    "steamMarriage": {
        "steamAchievementKey": "marriage", 
        "keyToCheck": "proposeGoal", 
        "key": "steamMarriage"
    }, 
    "exploreMenu": {
        "image": "trait-ede9e6_goal_goalExplore-000000", 
        "name": "COMPLETED - Explore City", 
        "key": "exploreMenu"
    }, 
    "cleanHouseMenu": {
        "image": "trait-ede9e6_goal_goalCleanHouse-000000", 
        "name": "COMPLETED - Hide Traces", 
        "key": "cleanHouseMenu"
    }, 
    "city": {
        "sector": [
            0, 
            1
        ], 
        "name": "City", 
        "image": "trait-ffaf3a", 
        "isaacEntities": [
            "travelButton", 
            "walkButton"
        ], 
        "key": "city", 
        "startManagedEntities": [
            "cityPathAdult"
        ], 
        "description": "New to you and not yet home"
    }, 
    "proposeMenu": {
        "image": "goal_goalPropose-008f90-000000", 
        "completeText": "Married!", 
        "name": "COMPLETED - Propose", 
        "key": "proposeMenu"
    }, 
    "escapeMenu": {
        "endGame": 1, 
        "completeText": "Building a new life gets easier every time.", 
        "name": "COMPLETED - Escape", 
        "key": "escapeMenu", 
        "image": "trait-ede9e6_goal_goalEscape-000000"
    }, 
    "personDPathMotherland": {
        "startKey": "personD", 
        "endKey": "motherland", 
        "key": "personDPathMotherland"
    }, 
    "memoryStopGoal": {
        "randomPosition": 1, 
        "description": "It takes effort to hold on to old emotions", 
        "timeToDeath": 150, 
        "currencyRequested": {
            "will": 3
        }, 
        "image": "goal_activeHex-000000", 
        "minSelfDist": 4, 
        "key": "memoryStopGoal", 
        "goalMenuKey": "memoryStopMenu", 
        "name": "Keep Memory Alive"
    }, 
    "meetPersonMenu": {
        "image": "goal_goalMeetNewPerson-000000", 
        "completeText": "It's always exciting to meet someone new!", 
        "name": "COMPLETED - Meet New Person", 
        "key": "meetPersonMenu"
    }, 
    "prideJoyMineConstruction": {
        "key": "prideJoyMineConstruction", 
        "constructionIdentifier": "prideJoyMine", 
        "name": "Doing Good", 
        "image": "tower_memoryDateGood-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Doing Good"
    }, 
    "tutorialIncludeWill": {
        "nextEntities": "tutorialFinishTrain", 
        "text": "Now click on the will mine to connect it", 
        "xOffset": 200, 
        "key": "tutorialIncludeWill", 
        "entityToMark": "willMine"
    }, 
    "flareMemoryMenu": {
        "image": "goal_chess-000000", 
        "name": "COMPLETED - Flare", 
        "key": "flareMemoryMenu"
    }, 
    "friend": {
        "buttonKeys": [
            "talkButton", 
            "impressMineButton"
        ], 
        "name": "friend", 
        "key": "friend"
    }, 
    "paintTower": {
        "name": "Paint", 
        "image": "tower_towerPaint", 
        "damageMultipliers": {
            "anger": 1, 
            "joy": 1, 
            "sadness": 1, 
            "fear": 1
        }, 
        "key": "paintTower", 
        "activeImage": "tower_towerPaint_activeOutline-0099ff", 
        "description": "Uses energy to calm all emotions"
    }, 
    "activationMenu": {
        "image": "trait-ede9e6_goal_goalActivation-000000", 
        "completeText": "You did not come here merely to drive cars and clean houses.", 
        "name": "COMPLETED - Activation", 
        "key": "activationMenu"
    }, 
    "chessTower": {
        "name": "Play Chess", 
        "image": "tower_towerChess", 
        "damageMultipliers": {
            "anger": 1, 
            "joy": 1, 
            "sadness": 1, 
            "fear": 1
        }, 
        "key": "chessTower", 
        "activeImage": "tower_towerChess_activeOutline-0099ff", 
        "description": "Uses thought to calm all emotions"
    }, 
    "memory_dateGreat": {
        "image": "trait-e6e617", 
        "memoryEmotions": [
            "joy"
        ], 
        "key": "memory_dateGreat", 
        "name": "Date Was Great"
    }, 
    "desireMineButton": {
        "constructionKey": "desireMineConstruction", 
        "name": "Aspiration", 
        "limitingKeys": [
            "desireMine", 
            "desireMineConstruction"
        ], 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_trophy", 
        "currenciesMade": [
            "desire"
        ], 
        "entityKey": "desireMine", 
        "maxTowerCount": 1, 
        "key": "desireMineButton", 
        "managerKey": "adult", 
        "description": "makes desire"
    }, 
    "readHomeMenu": {
        "image": "trait-ede9e6_goal_goalReadLetter-000000", 
        "completeText": "You still miss home, but you are here now and you have work to do.", 
        "name": "COMPLETED - Read Letter From Home", 
        "key": "readHomeMenu"
    }, 
    "flashback_fearConstruction": {
        "key": "flashback_fearConstruction", 
        "constructionIdentifier": "flashback_fear", 
        "name": "Flashback", 
        "image": "tower_flashback-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Flashback"
    }, 
    "calmDownGoal": {
        "key": "calmDownGoal", 
        "randomPosition": 1, 
        "name": "Calm Down", 
        "makeOnTimeOut": "shame", 
        "image": "trait-ede9e6_goal_goalCalmDown-008f90-000000", 
        "description": "It helps when you're here to calm things down", 
        "currencyRequested": {
            "sympathy": 2
        }, 
        "goalMenuKey": "calmDownMenu", 
        "timeToDeath": 150
    }, 
    "dateTower_personBConstruction": {
        "key": "dateTower_personBConstruction", 
        "constructionIdentifier": "dateTower_personB", 
        "name": "Constructing - Date", 
        "image": "trait-aaaaaa99_tower_towerDate", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Date", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "parkDateGoal": {
        "currencyEntities": {
            "dateGood": 20, 
            "dateSad": 20
        }, 
        "randomPosition": 0, 
        "name": "Amusement Park Date", 
        "image": "goal_goalPark-008f90-000000", 
        "description": {
            "personB": "You're just happier around this person.", 
            "personD": "Time together stimulates like with no one else"
        }, 
        "key": "parkDateGoal", 
        "currencyRequested": {
            "date": 2
        }, 
        "entitiesToMake": "dateComplete", 
        "goalMenuKey": "parkDateMenu", 
        "timeToDeath": 150
    }, 
    "target": {
        "name": "target", 
        "key": "target"
    }, 
    "consequenceSadMine": {
        "resourceCount": 2, 
        "name": "Consequences", 
        "image": "tower_memoryConsequence-000000", 
        "key": "consequenceSadMine", 
        "emotionTags": [
            "self", 
            "sadness"
        ], 
        "resourceKeys": [
            "sadness"
        ], 
        "activeImage": "tower_memoryConsequence-000000_activeOutline-0099ff", 
        "description": "You did not know - makes sadness"
    }, 
    "preparation": {
        "name": "preparation", 
        "key": "preparation", 
        "tags": [
            "preparation"
        ]
    }, 
    "paintButton": {
        "constructionKey": "paintTowerConstruction", 
        "name": "Paint", 
        "image": "tower_towerPaint", 
        "entityCost": {
            "energy": 5
        }, 
        "entityKey": "paintTower", 
        "subPanel": "Defenses", 
        "limitingKeys": [
            "paintTower", 
            "paintTowerConstruction"
        ], 
        "key": "paintButton", 
        "managerKey": "adult", 
        "description": "Uses energy to calm all emotions"
    }, 
    "impressMine_personDConstruction": {
        "key": "impressMine_personDConstruction", 
        "constructionIdentifier": "impressMine_personD", 
        "name": "Constructing - Desire To Impress", 
        "image": "trait-aaaaaa99_tower_mineImpress", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Desire To Impress", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "rechargeTowerConstruction": {
        "key": "rechargeTowerConstruction", 
        "constructionIdentifier": "rechargeTower", 
        "name": "Constructing - Recharge", 
        "image": "trait-aaaaaa99_tower_sun", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Recharge", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "openMindGoal": {
        "key": "openMindGoal", 
        "randomPosition": 1, 
        "name": "Moment To Breathe", 
        "image": "trait-ede9e6_goal_openMind-398a00-000000", 
        "startEntityKey": "self", 
        "currencyRequested": {
            "energy": 3
        }, 
        "entitiesToMake": "intelligent", 
        "goalMenuKey": "openMindMenu", 
        "description": "It's once again time to taste old pleasures."
    }, 
    "empathizeTowerConstruction": {
        "key": "empathizeTowerConstruction", 
        "constructionIdentifier": "empathizeTower", 
        "name": "Constructing - Empathize", 
        "image": "trait-aaaaaa99_tower_social", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Empathize", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "flashback_joy": {
        "resourceCount": 1, 
        "name": "Flashback", 
        "image": "tower_flashback-000000", 
        "key": "flashback_joy", 
        "emotionTags": "joy", 
        "resourceKeys": [
            "joy"
        ], 
        "activeImage": "tower_flashback-000000_activeOutline-0099ff", 
        "description": "makes joy"
    }, 
    "fantasyMine_personBConstruction": {
        "key": "fantasyMine_personBConstruction", 
        "constructionIdentifier": "fantasyMine_personB", 
        "name": "Constructing - Fantasize About", 
        "image": "trait-aaaaaa99_tower_mineFantasy", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Fantasize About", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "fadingMemory": {
        "mapSpawnKey": "memoryExtension", 
        "playerKillable": "true", 
        "timeToDeath": 300
    }, 
    "prepareAction": {
        "currenciesRequired": [
            "caution"
        ], 
        "currenciesToMake": [
            "preparation"
        ], 
        "towerKey": "prepareTower", 
        "name": "Prepare", 
        "key": "prepareAction"
    }, 
    "anger": {
        "emotion": "anger", 
        "name": "anger", 
        "image": "creep-ff0000", 
        "damage": 0, 
        "health": 10, 
        "key": "anger"
    }, 
    "newParticle": {
        "effectRadius": 25, 
        "image": "dot-ffd700", 
        "key": "newParticle"
    }, 
    "social_personB": {
        "person": "personB", 
        "name": "social", 
        "key": "social_personB", 
        "tags": [
            "social"
        ]
    }, 
    "memory_consequenceSad": {
        "image": "trait-013d70", 
        "memoryEmotions": [
            "sadness"
        ], 
        "key": "memory_consequenceSad", 
        "name": "Consequences"
    }, 
    "betrayalMine": {
        "resourceCount": 2, 
        "name": "Betrayal", 
        "image": "tower_memoryDefineSelf-000000", 
        "key": "betrayalMine", 
        "emotionTags": [
            "self", 
            "anger"
        ], 
        "resourceKeys": [
            "anger"
        ], 
        "activeImage": "tower_memoryDefineSelf-000000_activeOutline-0099ff", 
        "description": "They asked too much - makes anger"
    }, 
    "loseJobMenu": {
        "image": "trait-ede9e6_goal_goalJobQuota-000000", 
        "completeText": "You don't fill your quota, you don't keep your job.", 
        "name": "FAILED - Job Quota", 
        "key": "loseJobMenu"
    }, 
    "travelTower": {
        "image": "tower_towerTravel", 
        "activeImage": "tower_towerTravel_activeOutline-0099ff", 
        "name": "Travel", 
        "key": "travelTower", 
        "description": "Converts energy -> travel"
    }, 
    "self": {
        "name": "self", 
        "image": "trait_self", 
        "health": 100, 
        "key": "self", 
        "focusCount": 5, 
        "y": 0, 
        "x": 0
    }, 
    "confessionMenu": {
        "image": "trait-ede9e6_goal_goalConfession-000000", 
        "name": "COMPLETED - Confess", 
        "key": "confessionMenu"
    }, 
    "daydreamMine_personB": {
        "name": "Daydream", 
        "image": "tower_daydream", 
        "sourceTrait": "personB", 
        "key": "daydreamMine_personB", 
        "resourceKeys": [
            "energy"
        ], 
        "activeImage": "tower_daydream_activeOutline-0099ff"
    }, 
    "disavowSadnessMine": {
        "resourceCount": 3, 
        "name": "Alienation", 
        "image": "tower_memoryMissHome-000000", 
        "key": "disavowSadnessMine", 
        "emotionTags": [
            "self", 
            "sadness"
        ], 
        "resourceKeys": [
            "sadness"
        ], 
        "activeImage": "tower_memoryMissHome-000000_activeOutline-0099ff", 
        "description": "makes sadness"
    }, 
    "killFindJob": {
        "entitiesToKill": [
            "findCleanJobButton", 
            "findCabJobButton"
        ], 
        "key": "killFindJob"
    }, 
    "daydreamMine_personD": {
        "name": "Daydream", 
        "image": "tower_daydream", 
        "sourceTrait": "personD", 
        "key": "daydreamMine_personD", 
        "resourceKeys": [
            "energy"
        ], 
        "activeImage": "tower_daydream_activeOutline-0099ff"
    }, 
    "travelTowerConstruction": {
        "key": "travelTowerConstruction", 
        "constructionIdentifier": "travelTower", 
        "name": "Constructing - Travel", 
        "image": "trait-aaaaaa99_tower_towerTravel", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Travel", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "applyButton": {
        "constructionKey": "applyTowerConstruction", 
        "name": "Be Fueled By", 
        "image": "tower_motivation", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "applyTower", 
        "limitingKeys": [
            "applyTower", 
            "applyTowerConstruction"
        ], 
        "key": "applyButton", 
        "description": "Converts frustration -> energy"
    }, 
    "pain": {
        "name": "pain", 
        "key": "pain", 
        "tags": [
            "pain"
        ]
    }, 
    "disavowal": {
        "name": "disavowal", 
        "key": "disavowal", 
        "tags": [
            "disavowal"
        ]
    }, 
    "cleanWorkHouseGoal": {
        "currencyEntities": "prideJoy", 
        "randomPosition": 1, 
        "name": "Clean Lover's House", 
        "image": "trait-ede9e6_goal_goalCleanWorkHouse-000000", 
        "description": [
            "Your lover is too busy to worry about things like chores.", 
            "It feels good to help out.    "
        ], 
        "key": "cleanWorkHouseGoal", 
        "currencyRequested": {
            "chore": 2
        }, 
        "goalMenuKey": "cleanWorkHouseMenu", 
        "timeToDeath": 120
    }, 
    "industryMineConstruction": {
        "key": "industryMineConstruction", 
        "constructionIdentifier": "industryMine", 
        "name": "Constructing - Work Ethic", 
        "image": "trait-aaaaaa99_tower_mineIndustrious", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Work Ethic", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "lifeThreatMineConstruction": {
        "key": "lifeThreatMineConstruction", 
        "constructionIdentifier": "lifeThreatMine", 
        "name": "Fear for Life", 
        "image": "tower_memoryThreat-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Fear for Life"
    }, 
    "buyTimeGoalConstruction": {
        "key": "buyTimeGoalConstruction", 
        "constructionIdentifier": "buyTimeGoal", 
        "name": "Constructing - Buy Time", 
        "image": "trait-aaaaaa99_goal_goalBuyTime-008f90-000000", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Buy Time", 
        "killOnTransform": 1, 
        "description": "It takes will to start something new."
    }, 
    "confesssion_personB": {
        "person": "personB", 
        "name": "confession", 
        "key": "confesssion_personB", 
        "tags": [
            "confesssion"
        ]
    }, 
    "letter": {
        "name": "letter", 
        "key": "letter", 
        "tags": [
            "letter"
        ]
    }, 
    "parkPrepGoal": {
        "key": "parkPrepGoal", 
        "randomPosition": 0, 
        "name": "Prepare For Park Date", 
        "image": "goal_goalPark-008f90-000000", 
        "currencyRequested": {
            "cash": 1
        }, 
        "entitiesToMake": "parkDateGoal", 
        "goalMenuKey": "parkPrepMenu", 
        "timeToDeath": 150
    }, 
    "flirtTower_personD": {
        "name": "Flirt With", 
        "image": "tower_towerFlirt", 
        "sourceTrait": "personD", 
        "key": "flirtTower_personD", 
        "activeImage": "tower_towerFlirt_activeOutline-0099ff", 
        "description": "Converts social -> flirt"
    }, 
    "fightProgramGoal": {
        "currencyEntities": "defineSelf", 
        "randomPosition": 0, 
        "name": "Fight Programming", 
        "image": "goal_goalFightProgramming-000000", 
        "description": "Is this who you are?", 
        "key": "fightProgramGoal", 
        "startEntityKey": "motherland", 
        "currencyRequested": {
            "disavowal": 3
        }, 
        "goalMenuKey": "fightProgramMenu", 
        "timeToDeath": 120
    }, 
    "flirtTower_personB": {
        "name": "Flirt With", 
        "image": "tower_towerFlirt", 
        "sourceTrait": "personB", 
        "key": "flirtTower_personB", 
        "activeImage": "tower_towerFlirt_activeOutline-0099ff", 
        "description": "Converts social -> flirt"
    }, 
    "driveAction": {
        "currenciesRequired": [
            "desire"
        ], 
        "currenciesToMake": [
            "industry"
        ], 
        "towerKey": "driveTower", 
        "name": "drive", 
        "key": "driveAction"
    }, 
    "frustrationMineConstruction": {
        "key": "frustrationMineConstruction", 
        "constructionIdentifier": "frustrationMine", 
        "name": "Constructing - Frustration Mine", 
        "image": "trait-aaaaaa99_tower_mineFrustration", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Frustration Mine", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "planAction": {
        "currenciesRequired": [
            "thought"
        ], 
        "currenciesToMake": [
            "preparation"
        ], 
        "towerKey": "planTower", 
        "name": "plan", 
        "key": "planAction"
    }, 
    "restaurantDateGoal": {
        "currencyEntities": {
            "dateGood": 20, 
            "dateSad": 20
        }, 
        "randomPosition": 0, 
        "name": "Restaurant Date", 
        "image": "goal_goalRestaurant-008f90-000000", 
        "description": {
            "personB": "You're just happier around this person.", 
            "personD": "Time together stimulates like with no one else"
        }, 
        "key": "restaurantDateGoal", 
        "currencyRequested": {
            "date": 2
        }, 
        "entitiesToMake": "dateComplete", 
        "goalMenuKey": "restaurantDateMenu", 
        "timeToDeath": 150
    }, 
    "socialMine_personB": {
        "name": "Spend Time With", 
        "image": "tower_mineSocial", 
        "sourceTrait": "personB", 
        "key": "socialMine_personB", 
        "resourceKeys": [
            "social_personB"
        ], 
        "activeImage": "tower_mineSocial_activeOutline-0099ff"
    }, 
    "cheerMineButton": {
        "constructionKey": "cheerMineConstruction", 
        "name": "Cheer Mine", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_mineCheer", 
        "currenciesMade": [
            "cheer"
        ], 
        "entityKey": "cheerMine", 
        "limitingKeys": [
            "cheerMine", 
            "cheerMineConstruction"
        ], 
        "key": "cheerMineButton", 
        "description": "makes cheer"
    }, 
    "socialMine_personD": {
        "name": "Spend Time With", 
        "image": "tower_mineSocial", 
        "sourceTrait": "personD", 
        "key": "socialMine_personD", 
        "resourceKeys": [
            "social_personD"
        ], 
        "activeImage": "tower_mineSocial_activeOutline-0099ff"
    }, 
    "energyMineButton": {
        "constructionKey": "energyMineConstruction", 
        "name": "Energy Spring", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_energy", 
        "currenciesMade": [
            "energy"
        ], 
        "entityKey": "energyMine", 
        "limitingKeys": [
            "energyMine", 
            "energyMineConstruction"
        ], 
        "key": "energyMineButton", 
        "managerKey": "adult", 
        "description": "makes energy"
    }, 
    "planButton": {
        "constructionKey": "planTowerConstruction", 
        "name": "Plan", 
        "image": "tower_towerPlan", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "planTower", 
        "subPanel": "Towers", 
        "limitingKeys": [
            "planTower", 
            "planTowerConstruction"
        ], 
        "key": "planButton", 
        "managerKey": "intelligent", 
        "description": "Converts thought -> preparation"
    }, 
    "askOutSuccessMineConstruction": {
        "key": "askOutSuccessMineConstruction", 
        "constructionIdentifier": "askOutSuccessMine", 
        "name": "You have a date", 
        "image": "tower_memoryAskOutSuccess-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "You have a date"
    }, 
    "applyAction": {
        "currenciesRequired": [
            "frustration"
        ], 
        "currenciesToMake": [
            "energy"
        ], 
        "towerKey": "applyTower", 
        "name": "Apply", 
        "key": "applyAction"
    }, 
    "exploreGoal": {
        "key": "exploreGoal", 
        "randomPosition": 1, 
        "name": "Explore City", 
        "image": "trait-ede9e6_goal_goalExplore-000000", 
        "startEntityKey": "adult", 
        "currencyRequested": {
            "energy": 3
        }, 
        "entitiesToMake": [
            "city", 
            "findJob"
        ], 
        "goalMenuKey": "exploreMenu"
    }, 
    "socialGoalConstruction": {
        "key": "socialGoalConstruction", 
        "constructionIdentifier": "socialGoal", 
        "name": "Constructing - Socialize", 
        "image": "trait-aaaaaa99_goal_goalSocial-008f90-000000", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Socialize", 
        "killOnTransform": 1, 
        "description": "It takes will to start something new."
    }, 
    "cleanHouseGoal": {
        "currencyEntities": "fearFlash", 
        "randomPosition": 1, 
        "name": "Hide Traces", 
        "aliveEntities": "programming", 
        "image": "trait-ede9e6_goal_goalCleanHouse-000000", 
        "key": "cleanHouseGoal", 
        "startEntityKey": "motherland", 
        "currencyRequested": {
            "chore": 3
        }, 
        "goalMenuKey": "cleanHouseMenu", 
        "description": "No evidence can remain."
    }, 
    "confessFear": {
        "emotionsToAdd": {
            "fear": 2
        }, 
        "name": "Fear of Confession", 
        "image": "tower_memoryConfession-000000", 
        "key": "confessFear", 
        "memoryKey": "memory_confessFear", 
        "emotionTags": "self", 
        "resourceKey": "confessFearMineConstruction", 
        "activeImage": "tower_memoryConfession-000000_activeOutline-0099ff", 
        "description": "Will I be forgiven?"
    }, 
    "buyEscapeMenu": {
        "image": "trait-ede9e6_goal_goalEscape-000000", 
        "name": "COMPLETED - Prepare For Escape", 
        "key": "buyEscapeMenu"
    }, 
    "disavowalAction": {
        "towerKey": "disavowalTower", 
        "name": "Disavow", 
        "entitiesOnConvert": [
            "disavowAnger", 
            "disavowSadness"
        ], 
        "currenciesRequired": [
            "will", 
            "energy", 
            "frustration"
        ], 
        "currenciesToMake": [
            "disavowal"
        ], 
        "key": "disavowalAction"
    }, 
    "needsSupport": {
        "name": "needs support", 
        "key": "needsSupport", 
        "aliveEntities": {
            "cleanWorkHouseGoal": 3, 
            "calmDownGoal": 3
        }
    }, 
    "chessButton": {
        "constructionKey": "chessTowerConstruction", 
        "name": "Play Chess", 
        "image": "tower_towerChess", 
        "entityCost": {
            "thought": 5
        }, 
        "entityKey": "chessTower", 
        "limitingKeys": [
            "chessTower", 
            "chessTowerConstruction"
        ], 
        "key": "chessButton", 
        "subPanel": "Defenses", 
        "description": "Uses thought to calm all emotions"
    }, 
    "dateSadMine": {
        "resourceCount": 1, 
        "name": "Date Going Poorly", 
        "image": "tower_memoryDateSad-000000", 
        "key": "dateSadMine", 
        "emotionTags": [
            "self", 
            "sadness"
        ], 
        "resourceKeys": [
            "sadness"
        ], 
        "activeImage": "tower_memoryDateSad-000000_activeOutline-0099ff", 
        "description": "makes sadness"
    }, 
    "buyRingGoal": {
        "currencyEntities": "ringExcite", 
        "randomPosition": 0, 
        "name": "Buy Ring", 
        "currencyRequested": {
            "cash": 5
        }, 
        "image": "goal_goalBuyRing-008f90-000000", 
        "minSelfDist": 4, 
        "key": "buyRingGoal", 
        "entitiesToMake": "proposeGoal", 
        "goalMenuKey": "buyRingMenu", 
        "description": "This is definitely the one"
    }, 
    "toast": {
        "timeToDeath": 30, 
        "key": "toast"
    }, 
    "supportGoal": {
        "key": "supportGoal", 
        "randomPosition": 1, 
        "name": "Support", 
        "makeOnTimeOut": "shame", 
        "image": "trait-ede9e6_goal_goalSupport-008f90-000000", 
        "description": "Sometimes people just need someone to be there", 
        "currencyRequested": {
            "sympathy": 2
        }, 
        "entitiesToMake": "askAboutGoal", 
        "goalMenuKey": "supportMenu", 
        "timeToDeath": 150
    }, 
    "steamSpy": {
        "steamAchievementKey": "spy", 
        "keyToCheck": "spyGoal", 
        "key": "steamSpy"
    }, 
    "bombPrep1Goal": {
        "key": "bombPrep1Goal", 
        "randomPosition": 1, 
        "name": "Mission Prep 1", 
        "aliveEntities": "lifeThreat", 
        "image": "trait-ede9e6_goal_goalBombPrep-000000", 
        "makeOnTimeOut": [
            "killedMenu", 
            "gameEnd"
        ], 
        "startEntityKey": "motherland", 
        "currencyRequested": {
            "cash": 5
        }, 
        "description": [
            "Things are happening. The stakes are too high for you to mess this up.", 
            [
                "Failing this goal ends the game"
            ]
        ], 
        "entitiesToMake": [
            "bombPrep2Goal"
        ], 
        "goalMenuKey": "bombPrep1Menu", 
        "timeToDeath": 600
    }, 
    "socialMineButton_personD": {
        "constructionKey": "socialMine_personDConstruction", 
        "name": "Spend Time With", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_mineSocial", 
        "currenciesMade": [
            "social_personD"
        ], 
        "entityKey": "socialMine_personD", 
        "limitingKeys": [
            "socialMine_personD", 
            "socialMine_personDConstruction"
        ], 
        "key": "socialMineButton_personD", 
        "managerKey": "personD", 
        "description": "makes social"
    }, 
    "socialMineButton_personB": {
        "constructionKey": "socialMine_personBConstruction", 
        "name": "Spend Time With", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_mineSocial", 
        "currenciesMade": [
            "social_personB"
        ], 
        "entityKey": "socialMine_personB", 
        "limitingKeys": [
            "socialMine_personB", 
            "socialMine_personBConstruction"
        ], 
        "key": "socialMineButton_personB", 
        "managerKey": "personB", 
        "description": "makes social"
    }, 
    "driveTowerConstruction": {
        "key": "driveTowerConstruction", 
        "constructionIdentifier": "driveTower", 
        "name": "Constructing - Drive", 
        "image": "trait-aaaaaa99_tower_drive", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Drive", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "worldGoalFocusPathStopComponent": {
        "currencyEffect": "starParticleEffect"
    }, 
    "rechargeButton": {
        "constructionKey": "rechargeTowerConstruction", 
        "name": "Recharge", 
        "image": "tower_sun", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "rechargeTower", 
        "limitingKeys": [
            "rechargeTower", 
            "rechargeTowerConstruction"
        ], 
        "key": "rechargeButton", 
        "description": "Converts cheer -> energy"
    }, 
    "socialMenu": {
        "image": "goal_goalSocial-008f90-000000", 
        "name": "COMPLETED - Socialize", 
        "key": "socialMenu"
    }, 
    "chapter5": {
        "name": "Chapter 5 - Escalation", 
        "makeOnCreateEntities": [
            "spyGoal", 
            "planButton", 
            "chapter5Menu"
        ], 
        "chapterCount": 5, 
        "key": "chapter5", 
        "entitiesToMake": [
            "chapter6"
        ], 
        "goalsToCheck": {
            "spyGoal": 1
        }
    }, 
    "chapter4": {
        "name": "Chapter 4 - The New Normal", 
        "makeOnCreateEntities": [
            "cleanHouseGoal", 
            "chapter4Menu"
        ], 
        "chapterCount": 4, 
        "key": "chapter4", 
        "entitiesToMake": [
            "chapter5"
        ], 
        "goalsToCheck": {
            "cleanHouseGoal": 1
        }
    }, 
    "chapter6": {
        "chapterCount": 6, 
        "makeOnCreateEntities": [
            "bombPrep1Goal", 
            "chapter6Menu"
        ], 
        "name": "Chapter 6 - Zenith", 
        "key": "chapter6", 
        "goalsToCheck": {
            "nullGoal": 1
        }
    }, 
    "chapter1": {
        "name": "Chapter 1 - Moving In", 
        "makeOnCreateEntities": [
            "adult", 
            "wakeGoal"
        ], 
        "chapterCount": 1, 
        "key": "chapter1", 
        "entitiesToMake": [
            "chapter2"
        ], 
        "goalsToCheck": {
            "readHomeGoal": 1, 
            "exploreGoal": 1
        }
    }, 
    "workShiftMenu": {
        "image": "goal_goalJobQuota-000000", 
        "completeText": "Another day, another dollar.", 
        "name": "COMPLETED - Work Shift", 
        "key": "workShiftMenu"
    }, 
    "chapter3": {
        "name": "Chapter 3 - It Begins", 
        "makeOnCreateEntities": [
            "activationGoal", 
            "openMindGoal", 
            "chapter3Menu"
        ], 
        "chapterCount": 3, 
        "key": "chapter3", 
        "entitiesToMake": [
            "chapter4"
        ], 
        "goalsToCheck": {
            "papersGoal": 1
        }
    }, 
    "chapter2": {
        "name": "Chapter 2 - Fitting In", 
        "makeOnCreateEntities": [
            "meetPersonButton", 
            "chapter2Menu"
        ], 
        "chapterCount": 2, 
        "key": "chapter2", 
        "entitiesToMake": [
            "chapter3"
        ], 
        "goalsToCheck": {
            "meetPersonGoal": 1
        }
    }, 
    "talkTower_personB": {
        "description": "Uses social to calm all emotions", 
        "image": "tower_towerTalk", 
        "damage": 20, 
        "damageMultipliers": {
            "anger": 1, 
            "joy": 1, 
            "sadness": 1, 
            "fear": 1
        }, 
        "sourceTrait": "personB", 
        "key": "talkTower_personB", 
        "activeImage": "tower_towerTalk_activeOutline-0099ff", 
        "name": "Vent To"
    }, 
    "spyButton": {
        "constructionKey": "spyTowerConstruction", 
        "name": "Spy", 
        "limitingKeys": [
            "spyTower", 
            "spyTowerConstruction"
        ], 
        "image": "tower_towerSpy", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "spyTower", 
        "subPanel": "Towers", 
        "maxTowerCount": 1, 
        "key": "spyButton", 
        "managerKey": "motherland", 
        "description": "Converts travel + preparation -> spy"
    }, 
    "talkTower_personD": {
        "description": "Uses social to calm all emotions", 
        "image": "tower_towerTalk", 
        "damage": 20, 
        "damageMultipliers": {
            "anger": 1, 
            "joy": 1, 
            "sadness": 1, 
            "fear": 1
        }, 
        "sourceTrait": "personD", 
        "key": "talkTower_personD", 
        "activeImage": "tower_towerTalk_activeOutline-0099ff", 
        "name": "Vent To"
    }, 
    "confessTower_personB": {
        "name": "Confess To", 
        "image": "tower_towerConfess", 
        "sourceTrait": "personB", 
        "key": "confessTower_personB", 
        "activeImage": "tower_towerConfess_activeOutline-0099ff", 
        "description": "Converts sin -> confession"
    }, 
    "confessTower_personD": {
        "name": "Confess To", 
        "image": "tower_towerConfess", 
        "sourceTrait": "personD", 
        "key": "confessTower_personD", 
        "activeImage": "tower_towerConfess_activeOutline-0099ff", 
        "description": "Converts sin -> confession"
    }, 
    "married": {
        "buttonKeys": [
            "confessButton"
        ], 
        "name": "married", 
        "key": "married"
    }, 
    "socialMine_personBConstruction": {
        "key": "socialMine_personBConstruction", 
        "constructionIdentifier": "socialMine_personB", 
        "name": "Constructing - Spend Time With", 
        "image": "trait-aaaaaa99_tower_mineSocial", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Spend Time With", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "understandTowerConstruction": {
        "key": "understandTowerConstruction", 
        "constructionIdentifier": "understandTower", 
        "name": "Constructing - Understand", 
        "image": "trait-aaaaaa99_tower_understand", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Understand", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "workChoreMenu": {
        "image": "trait-ede9e6_goal_goalCabChore-000000", 
        "name": "COMPLETED - Work Chores", 
        "key": "workChoreMenu"
    }, 
    "lostControl": {
        "name": "lostControl", 
        "key": "lostControl"
    }, 
    "thinkAboutButton": {
        "constructionKey": "thinkAboutTowerConstruction", 
        "name": "Think About", 
        "image": "tower_towerThinkAbout", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "thinkAboutTower", 
        "limitingKeys": [
            "thinkAboutTower", 
            "thinkAboutTowerConstruction"
        ], 
        "key": "thinkAboutButton", 
        "subPanel": "Towers", 
        "description": "Converts thought -> sympathy"
    }, 
    "worldGoal": {
        "minSelfDist": 4
    }, 
    "understandMemoryButton": {
        "constructionKey": "understandMemoryGoalConstruction", 
        "name": "Understand", 
        "image": "chess", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "understandMemoryGoal", 
        "key": "understandMemoryButton", 
        "description": "Requires understand."
    }, 
    "missionMenu": {
        "endGame": 1, 
        "completeText": "This is what you will be known for.", 
        "name": "COMPLETED - Mission", 
        "key": "missionMenu", 
        "image": "trait-ede9e6_goal_goalMission-000000"
    }, 
    "flirtButton_personD": {
        "constructionKey": "flirtTower_personDConstruction", 
        "name": "Flirt With", 
        "image": "tower_towerFlirt", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "flirtTower_personD", 
        "personHeld": "personD", 
        "managerKey": "personD", 
        "limitingKeys": [
            "flirtTower_personD", 
            "flirtTower_personDConstruction"
        ], 
        "key": "flirtButton_personD", 
        "subPanel": "Towers", 
        "description": "Converts social -> flirt"
    }, 
    "flirtButton_personB": {
        "constructionKey": "flirtTower_personBConstruction", 
        "name": "Flirt With", 
        "image": "tower_towerFlirt", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "flirtTower_personB", 
        "personHeld": "personB", 
        "managerKey": "personB", 
        "limitingKeys": [
            "flirtTower_personB", 
            "flirtTower_personBConstruction"
        ], 
        "key": "flirtButton_personB", 
        "subPanel": "Towers", 
        "description": "Converts social -> flirt"
    }, 
    "memoryGoal": {
        "minSelfDist": 4
    }, 
    "gainControl_joy": {
        "barColor": "cccc99", 
        "text": "It's hard to control yourself when you're this happy.", 
        "textColor": "dddd00", 
        "key": "gainControl_joy"
    }, 
    "disavowalButton": {
        "constructionKey": "disavowalTowerConstruction", 
        "name": "Disavow", 
        "limitingKeys": [
            "disavowalTower", 
            "disavowalTowerConstruction"
        ], 
        "image": "tower_towerDisavowal", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "disavowalTower", 
        "subPanel": "Towers", 
        "maxTowerCount": 1, 
        "key": "disavowalButton", 
        "managerKey": "motherland", 
        "description": "Converts will + energy + frustration -> disavowal"
    }, 
    "project": {
        "playerKillable": "true"
    }, 
    "memory_nastyComment": {
        "image": "trait-aa1717", 
        "memoryEmotions": [
            "anger"
        ], 
        "key": "memory_nastyComment", 
        "name": "Casual Racism"
    }, 
    "steamJob": {
        "steamAchievementKey": "job", 
        "keyToCheck": "workShiftGoal", 
        "key": "steamJob"
    }, 
    "personHealAction": {
        "health": 0
    }, 
    "tutorialFinishGoalTrain": {
        "nextEntities": "tutorialFinish", 
        "text": "And again to the self to complete the path", 
        "yOffset": 100, 
        "entityToMark": "self", 
        "key": "tutorialFinishGoalTrain", 
        "xOffset": 400
    }, 
    "supportGoalConstruction": {
        "key": "supportGoalConstruction", 
        "constructionIdentifier": "supportGoal", 
        "name": "Constructing - Support", 
        "image": "trait-aaaaaa99_goal_goalSupport-008f90-000000", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Support", 
        "killOnTransform": 1, 
        "description": "It takes will to start something new."
    }, 
    "joy": {
        "emotion": "joy", 
        "name": "joy", 
        "image": "creep-ffff00", 
        "damage": 0, 
        "health": 10, 
        "key": "joy"
    }, 
    "activationGoal": {
        "key": "activationGoal", 
        "randomPosition": 1, 
        "name": "Activation", 
        "aliveEntities": "programming", 
        "image": "trait-ede9e6_goal_goalActivation-000000", 
        "startEntityKey": "self", 
        "currencyRequested": {
            "travel": 3
        }, 
        "entitiesToMake": [
            "agent", 
            "learnFirstMission", 
            "fightProgramButton"
        ], 
        "goalMenuKey": "activationMenu", 
        "description": "You are not where you need to be."
    }, 
    "contactTower_personB": {
        "name": "Contact", 
        "image": "tower_towerContact", 
        "sourceTrait": "personB", 
        "key": "contactTower_personB", 
        "activeImage": "tower_towerContact_activeOutline-0099ff", 
        "description": "Converts social -> contact"
    }, 
    "cautionMineConstruction": {
        "key": "cautionMineConstruction", 
        "constructionIdentifier": "cautionMine", 
        "name": "Constructing - Caution Mine", 
        "image": "trait-aaaaaa99_tower_mineCaution", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Caution Mine", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "disablePersonD": {
        "timeToDeath": 15, 
        "name": "This person just needs some time alone to work right now.", 
        "key": "disablePersonD", 
        "traitsToDisable": [
            "personD"
        ]
    }, 
    "cleanChoreMenu": {
        "image": "trait-ede9e6_goal_goalCleanChore-000000", 
        "name": "COMPLETED - Favor for Boss", 
        "key": "cleanChoreMenu"
    }, 
    "paintTowerConstruction": {
        "key": "paintTowerConstruction", 
        "constructionIdentifier": "paintTower", 
        "name": "Constructing - Paint", 
        "image": "trait-aaaaaa99_tower_towerPaint", 
        "currencyRequested": {
            "energy": 5
        }, 
        "constructionName": "Paint", 
        "killOnTransform": 0
    }, 
    "contactTower_personD": {
        "name": "Contact", 
        "image": "tower_towerContact", 
        "sourceTrait": "personD", 
        "key": "contactTower_personD", 
        "activeImage": "tower_towerContact_activeOutline-0099ff", 
        "description": "Converts social -> contact"
    }, 
    "cabChoreMenu": {
        "image": "trait-ede9e6_goal_goalCabChore-000000", 
        "name": "COMPLETED - Favor for Boss", 
        "key": "cabChoreMenu"
    }, 
    "memory_disavowAnger": {
        "image": "trait-aa1717", 
        "memoryEmotions": [
            "anger"
        ], 
        "key": "memory_disavowAnger", 
        "name": "Alienation"
    }, 
    "acceptFearMine": {
        "resourceCount": 1, 
        "name": "Will this work?", 
        "image": "tower_memoryAcceptFear-000000", 
        "key": "acceptFearMine", 
        "emotionTags": [
            "self", 
            "fear"
        ], 
        "resourceKeys": [
            "fear"
        ], 
        "activeImage": "tower_memoryAcceptFear-000000_activeOutline-0099ff", 
        "description": "makes fear"
    }, 
    "findCleanJobGoal": {
        "currencyEntities": "nastyComment", 
        "randomPosition": 0, 
        "name": "Become Cleaner", 
        "image": "goal_goalCleaner-000000", 
        "key": "findCleanJobGoal", 
        "startEntityKey": "city", 
        "currencyRequested": {
            "travel": 3
        }, 
        "entitiesToMake": [
            "cleanButton", 
            "job", 
            "workShiftButton", 
            "killFindJob"
        ], 
        "goalMenuKey": "findCleanJobMenu", 
        "description": "No one likes foreigners, but some people will employ them."
    }, 
    "learnSadMine": {
        "resourceCount": 2, 
        "name": "Learnings", 
        "image": "tower_memoryLearn-000000", 
        "key": "learnSadMine", 
        "emotionTags": [
            "self", 
            "sadness"
        ], 
        "resourceKeys": [
            "sadness"
        ], 
        "activeImage": "tower_memoryLearn-000000_activeOutline-0099ff", 
        "description": "Other people's wounds can hurt too - makes sadness"
    }, 
    "job": {
        "image": "disabledTower", 
        "dayCount": 4, 
        "name": "Job Quota", 
        "key": "job", 
        "dayStartEntity": "jobQuotaGoal"
    }, 
    "restaurantDateMenu": {
        "image": "goal_goalRestaurant-008f90-000000", 
        "completeText": "Good food. Better company.", 
        "name": "COMPLETED - Restaurant Date", 
        "key": "restaurantDateMenu"
    }, 
    "flashback_anger": {
        "resourceCount": 1, 
        "name": "Flashback", 
        "image": "tower_flashback-000000", 
        "key": "flashback_anger", 
        "emotionTags": "anger", 
        "resourceKeys": [
            "anger"
        ], 
        "activeImage": "tower_flashback-000000_activeOutline-0099ff", 
        "description": "makes anger"
    }, 
    "dateLucky": {
        "emotionsToAdd": {
            "joy": 3
        }, 
        "name": "Feeling Lucky", 
        "image": "tower_memoryRing", 
        "key": "dateLucky", 
        "memoryKey": "memory_dateLucky", 
        "emotionTags": "self", 
        "resourceKey": "dateLuckyMineConstruction", 
        "activeImage": "tower_memoryRing_activeOutline-0099ff", 
        "description": "How did you get this lucky?"
    }, 
    "tutorialPreDefense": {
        "nextEntities": "tutorialDefense", 
        "text": "First, close this menu", 
        "yOffset": 580, 
        "entityToMark": "unpack", 
        "key": "tutorialPreDefense", 
        "xOffset": 435
    }, 
    "gainControl_sadness": {
        "barColor": "9999cc", 
        "text": "You need some time to push through this sadness.", 
        "textColor": 66, 
        "key": "gainControl_sadness"
    }, 
    "flashback_sadnessConstruction": {
        "key": "flashback_sadnessConstruction", 
        "constructionIdentifier": "flashback_sadness", 
        "name": "Flashback", 
        "image": "tower_flashback-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Flashback"
    }, 
    "memory_programming": {
        "image": "trait-7533f2", 
        "memoryEmotions": [
            "fear"
        ], 
        "key": "memory_programming", 
        "name": "Programming"
    }, 
    "askAboutGoalConstruction": {
        "key": "askAboutGoalConstruction", 
        "constructionIdentifier": "askAboutGoal", 
        "name": "Constructing - Ask About", 
        "image": "trait-aaaaaa99_goal_goalAskAbout-008f90-000000", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Ask About", 
        "killOnTransform": 1, 
        "description": "It takes will to start something new."
    }, 
    "escapeGoal": {
        "key": "escapeGoal", 
        "randomPosition": 0, 
        "name": "Escape", 
        "image": "trait-ede9e6_goal_goalEscape-000000", 
        "startEntityKey": "self", 
        "currencyRequested": {
            "travel": 10
        }, 
        "entitiesToMake": [
            "gameEnd"
        ], 
        "goalMenuKey": "escapeMenu"
    }, 
    "cabTower": {
        "image": "tower_towerCab", 
        "activeImage": "tower_towerCab_activeOutline-0099ff", 
        "name": "Drive", 
        "key": "cabTower", 
        "description": "Converts travel + industry -> work"
    }, 
    "tutorialTextEnergyPanel2": {
        "text": "To get this will, you need to start a new train", 
        "yOffset": 320, 
        "xOffset": 435, 
        "key": "tutorialTextEnergyPanel2", 
        "entityToMark": "energyMine"
    }, 
    "askOutSuccessMine": {
        "resourceCount": 3, 
        "name": "You have a date", 
        "image": "tower_memoryAskOutSuccess", 
        "key": "askOutSuccessMine", 
        "emotionTags": [
            "self", 
            "joy"
        ], 
        "resourceKeys": [
            "joy"
        ], 
        "activeImage": "tower_memoryAskOutSuccess_activeOutline-0099ff", 
        "description": "makes joy"
    }, 
    "cleanWorkHouseMenu": {
        "image": "trait-ede9e6_goal_goalCleanWorkHouse-000000", 
        "name": "COMPLETED - Clean Lover's House", 
        "key": "cleanWorkHouseMenu"
    }, 
    "contactTower_personBConstruction": {
        "key": "contactTower_personBConstruction", 
        "constructionIdentifier": "contactTower_personB", 
        "name": "Constructing - Contact", 
        "image": "trait-aaaaaa99_tower_towerContact", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Contact", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "choreTowerConstruction": {
        "key": "choreTowerConstruction", 
        "constructionIdentifier": "choreTower", 
        "name": "Constructing - Do Chores", 
        "image": "trait-aaaaaa99_tower_towerChores", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Do Chores", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "learnAboutGoal": {
        "currencyEntities": "learnSad", 
        "randomPosition": 0, 
        "name": "Learn About", 
        "image": "trait-ede9e6_goal_goalLearnAbout-008f90-000000", 
        "key": "learnAboutGoal", 
        "currencyRequested": {
            "social": 3
        }, 
        "goalMenuKey": "learnAboutMenu", 
        "description": "Who is this person?"
    }, 
    "dateReplace": {
        "goodEntities": {
            "parkPrepGoal": 1, 
            "restaurantPrepGoal": 1, 
            "walkDateGoal": 1
        }, 
        "key": "dateReplace"
    }, 
    "justTalkMenu": {
        "image": "trait-ede9e6_goal_goalChat-008f90-000000", 
        "name": "COMPLETED - Just Talk", 
        "key": "justTalkMenu"
    }, 
    "programmingMineConstruction": {
        "key": "programmingMineConstruction", 
        "constructionIdentifier": "programmingMine", 
        "name": "Programming", 
        "image": "tower_memoryProgramming-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Programming"
    }, 
    "contactButton": {
        "constructionKey": "contactTowerConstruction", 
        "name": "Contact", 
        "limitingKeys": [
            "contactTower", 
            "contactTowerConstruction"
        ], 
        "image": "tower_towerContact", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "contactTower", 
        "subPanel": "Towers", 
        "maxTowerCount": 1, 
        "key": "contactButton", 
        "managerKey": "motherland", 
        "description": "Converts travel -> contact"
    }, 
    "memory_threat": {
        "image": "trait-7533f2", 
        "memoryEmotions": [
            "fear"
        ], 
        "key": "memory_threat", 
        "name": "Threat"
    }, 
    "missHomeMine": {
        "resourceCount": 1, 
        "name": "Missing Home", 
        "image": "tower_memoryMissHome-000000", 
        "key": "missHomeMine", 
        "emotionTags": [
            "self", 
            "sadness"
        ], 
        "resourceKeys": [
            "sadness"
        ], 
        "activeImage": "tower_memoryMissHome-000000_activeOutline-0099ff", 
        "description": "makes sadness"
    }, 
    "city_ext": {
        "image": [
            "trait-ffaf3a", 
            "trait-E59D34", 
            "trait-FFC03F"
        ], 
        "name": "City", 
        "key": "city_ext", 
        "sourceKey": "city"
    }, 
    "worker": {
        "planeOfExistence": "path"
    }, 
    "learnSadMineConstruction": {
        "key": "learnSadMineConstruction", 
        "constructionIdentifier": "learnSadMine", 
        "name": "Learnings", 
        "image": "tower_memoryLearn-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Learnings"
    }, 
    "understand": {
        "name": "understand", 
        "key": "understand", 
        "tags": [
            "understand"
        ]
    }, 
    "singleHealthComponent": {
        "health": 1
    }, 
    "readHomeGoal": {
        "currencyEntities": "missHome", 
        "randomPosition": 1, 
        "name": "Read Letter From Home", 
        "image": "trait-ede9e6_goal_goalReadLetter-000000", 
        "key": "readHomeGoal", 
        "startEntityKey": "adult", 
        "currencyRequested": {
            "will": 3
        }, 
        "entitiesToMake": [
            "motherland"
        ], 
        "goalMenuKey": "readHomeMenu"
    }, 
    "wakeGoal": {
        "key": "wakeGoal", 
        "randomPosition": 1, 
        "name": "Wake Up", 
        "image": "trait-ede9e6_goal_goalWakeUp-000000", 
        "startEntityKey": "adult", 
        "currencyRequested": {
            "energy": 2
        }, 
        "entitiesToMake": [
            "unpackGoal", 
            "choreButton"
        ], 
        "goalMenuKey": "wakeMenu"
    }, 
    "fightProgramGoalConstruction": {
        "key": "fightProgramGoalConstruction", 
        "constructionIdentifier": "fightProgramGoal", 
        "name": "Constructing - Fight Programming", 
        "image": "trait-aaaaaa99_goal_goalFightProgramming-000000", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Fight Programming", 
        "killOnTransform": 1, 
        "description": "It takes will to start something new."
    }, 
    "memory_askOutSuccess": {
        "image": "trait-e6e617", 
        "memoryEmotions": [
            "joy"
        ], 
        "key": "memory_askOutSuccess", 
        "name": "You have a date"
    }, 
    "worried": {
        "successOpinions": {
            "insecure": 60
        }, 
        "name": "worried", 
        "key": "worried"
    }, 
    "acceptFear": {
        "emotionsToAdd": {
            "fear": 1
        }, 
        "name": "Will this work?", 
        "image": "tower_memoryAcceptFear-000000", 
        "key": "acceptFear", 
        "memoryKey": "memory_acceptFear", 
        "emotionTags": "self", 
        "resourceKey": "acceptFearMineConstruction", 
        "activeImage": "tower_memoryAcceptFear-000000_activeOutline-0099ff"
    }, 
    "cash": {
        "persists": 1, 
        "name": "cash", 
        "key": "cash", 
        "tags": [
            "cash"
        ]
    }, 
    "choreAction": {
        "currenciesRequired": [
            "energy"
        ], 
        "currenciesToMake": [
            "chore"
        ], 
        "towerKey": "choreTower", 
        "name": "Chore", 
        "key": "choreAction"
    }, 
    "will": {
        "name": "will", 
        "key": "will", 
        "tags": [
            "will"
        ]
    }, 
    "nullReplace": {
        "key": "nullReplace"
    }, 
    "dateSadMineConstruction": {
        "key": "dateSadMineConstruction", 
        "constructionIdentifier": "dateSadMine", 
        "name": "Date Going Poorly", 
        "image": "tower_memoryDateSad-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Date Going Poorly"
    }, 
    "unpackMenu": {
        "image": "trait-ede9e6_goal_goalUnpack-000000", 
        "name": "COMPLETED - Unpack", 
        "key": "unpackMenu"
    }, 
    "flashback_fear": {
        "resourceCount": 1, 
        "name": "Flashback", 
        "image": "tower_flashback-000000", 
        "key": "flashback_fear", 
        "emotionTags": "fear", 
        "resourceKeys": [
            "fear"
        ], 
        "activeImage": "tower_flashback-000000_activeOutline-0099ff", 
        "description": "makes fear"
    }, 
    "motherlandPathAdult": {
        "startKey": "motherland", 
        "endKey": "adult", 
        "key": "motherlandPathAdult"
    }, 
    "dateFailMine": {
        "resourceCount": 4, 
        "name": "Date Wasn't Perfect", 
        "image": "tower_memoryDateFail-000000", 
        "key": "dateFailMine", 
        "emotionTags": [
            "self", 
            "sadness"
        ], 
        "resourceKeys": [
            "sadness"
        ], 
        "activeImage": "tower_memoryDateFail-000000_activeOutline-0099ff", 
        "description": "makes sadness"
    }, 
    "shame": {
        "emotionsToAdd": {
            "sadness": 3
        }, 
        "name": "Shame", 
        "image": "tower_memoryShame-000000", 
        "key": "shame", 
        "memoryKey": "memory_shame", 
        "emotionTags": "self", 
        "resourceKey": "shameMineConstruction", 
        "activeImage": "tower_memoryShame-000000_activeOutline-0099ff"
    }, 
    "defineSelfMineConstruction": {
        "key": "defineSelfMineConstruction", 
        "constructionIdentifier": "defineSelfMine", 
        "name": "Define Yourself", 
        "image": "tower_memoryDefineSelf-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Define Yourself"
    }, 
    "experience": {
        "name": "experience", 
        "key": "experience", 
        "tags": [
            "experience"
        ]
    }, 
    "justTalkGoal": {
        "key": "justTalkGoal", 
        "randomPosition": 1, 
        "name": "Just Talk", 
        "makeOnTimeOut": "shame", 
        "image": "trait-ede9e6_goal_goalChat-008f90-000000", 
        "description": "It's nice to just sit and talk for a bit.", 
        "currencyRequested": {
            "social": 3
        }, 
        "goalMenuKey": "justTalkMenu", 
        "timeToDeath": 150
    }, 
    "findJob": {
        "entitiesToMake": [
            "findCleanJobButton", 
            "findCabJobButton"
        ], 
        "key": "findJob"
    }, 
    "social_personD": {
        "person": "personD", 
        "name": "social", 
        "key": "social_personD", 
        "tags": [
            "social"
        ]
    }, 
    "cleanTowerConstruction": {
        "key": "cleanTowerConstruction", 
        "constructionIdentifier": "cleanTower", 
        "name": "Constructing - Clean", 
        "image": "trait-aaaaaa99_tower_towerClean", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Clean", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "personGoal": {
        "minSelfDist": 4
    }, 
    "walkAction": {
        "currenciesRequired": [
            "energy"
        ], 
        "towerKey": "walkTower", 
        "name": "walk", 
        "key": "walkAction"
    }, 
    "confessButton_personD": {
        "constructionKey": "confessTower_personDConstruction", 
        "name": "Confess To", 
        "image": "tower_towerConfess", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "confessTower_personD", 
        "personHeld": "personD", 
        "managerKey": "personD", 
        "limitingKeys": [
            "confessTower_personD", 
            "confessTower_personDConstruction"
        ], 
        "key": "confessButton_personD", 
        "subPanel": "Towers", 
        "description": "Converts sin -> confession"
    }, 
    "wakeMenu": {
        "image": "trait-ede9e6_goal_goalWakeUp-000000", 
        "name": "COMPLETED - Wake Up", 
        "key": "wakeMenu"
    }, 
    "spyGoal": {
        "currencyEntities": "fearFlash", 
        "randomPosition": 1, 
        "name": "Spy", 
        "aliveEntities": "programming", 
        "image": "trait-ede9e6_goal_goalSpy-000000", 
        "key": "spyGoal", 
        "startEntityKey": "motherland", 
        "currencyRequested": {
            "spy": 3
        }, 
        "entitiesToMake": [
            "spyResult"
        ], 
        "goalMenuKey": "spyMenu", 
        "description": [
            "The local teacher has become more vocal of late.", 
            "What is she up to?"
        ]
    }, 
    "cleanCompete": {
        "entitiesToKill": [
            "cleanPride"
        ], 
        "goalKey": "cleanAction", 
        "startEntityKey": "city", 
        "name": "Promotion", 
        "key": "cleanCompete"
    }, 
    "askOutGoal": {
        "currencyEntities": "acceptFear", 
        "opinionToAdd": "dating", 
        "name": "Ask For Date", 
        "image": "goal_goalAskOut-008f90-000000", 
        "randomPosition": 0, 
        "key": "askOutGoal", 
        "currencyRequested": {
            "flirt": 3
        }, 
        "entitiesToMake": "dateReplace", 
        "goalMenuKey": "askOutMenu", 
        "description": {
            "personB": "You want that smile in your life", 
            "personD": "It is nice to have someone that understands"
        }
    }, 
    "willMineButton": {
        "constructionKey": "willMineConstruction", 
        "name": "Will Mine", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_mineWill", 
        "currenciesMade": [
            "will"
        ], 
        "entityKey": "willMine", 
        "limitingKeys": [
            "willMine", 
            "willMineConstruction"
        ], 
        "key": "willMineButton", 
        "managerKey": "adult", 
        "description": "makes will"
    }, 
    "frustrationMineButton": {
        "constructionKey": "frustrationMineConstruction", 
        "name": "Frustration Mine", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_mineFrustration", 
        "currenciesMade": [
            "frustration"
        ], 
        "entityKey": "frustrationMine", 
        "limitingKeys": [
            "frustrationMine", 
            "frustrationMineConstruction"
        ], 
        "key": "frustrationMineButton", 
        "description": "makes frustration"
    }, 
    "cautionMineButton": {
        "constructionKey": "cautionMineConstruction", 
        "name": "Caution Mine", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_mineCaution", 
        "currenciesMade": [
            "caution"
        ], 
        "entityKey": "cautionMine", 
        "limitingKeys": [
            "cautionMine", 
            "cautionMineConstruction"
        ], 
        "key": "cautionMineButton", 
        "description": "makes caution"
    }, 
    "gainControlMenu": {
        "y": 164, 
        "x": 237
    }, 
    "killWork": {
        "entitiesToKill": [
            "cleanTower", 
            "cabTower", 
            "cleanButton", 
            "cabButton"
        ], 
        "key": "killWork"
    }, 
    "desireMine": {
        "name": "Aspiration", 
        "image": "tower_trophy", 
        "sourceTrait": "adult", 
        "key": "desireMine", 
        "resourceKeys": [
            "desire"
        ], 
        "activeImage": "tower_trophy_activeOutline-0099ff"
    }, 
    "talkTower_personBConstruction": {
        "key": "talkTower_personBConstruction", 
        "constructionIdentifier": "talkTower_personB", 
        "name": "Constructing - Vent To", 
        "image": "trait-aaaaaa99_tower_towerTalk", 
        "currencyRequested": {
            "social_personB": 5
        }, 
        "constructionName": "Vent To", 
        "killOnTransform": 0
    }, 
    "personB": {
        "sector": [
            0, 
            2
        ], 
        "knownPersonalityTraits": [
            "adult", 
            "city"
        ], 
        "heldOpinions": [
            "acquaintance", 
            "worried", 
            "crush"
        ], 
        "description": "Cute and with a face tilt that you adore. How could you resist?", 
        "image": "trait-68cccc", 
        "key": "personB", 
        "startManagedEntities": [
            "personBPathCity", 
            "personBPathMotherland"
        ], 
        "name": "The Pretty Person"
    }, 
    "personD": {
        "sector": [
            2, 
            0
        ], 
        "knownPersonalityTraits": [
            "adult", 
            "city"
        ], 
        "heldOpinions": [
            "acquaintance", 
            "crush", 
            "hasProject"
        ], 
        "description": "So very smart. So very sharp. Your kind of challenge.", 
        "image": "trait-43cce5", 
        "key": "personD", 
        "startManagedEntities": [
            "personDPathCity", 
            "personDPathMotherland"
        ], 
        "name": "The One You Admire"
    }, 
    "workShiftButton": {
        "constructionKey": "workShiftGoalConstruction", 
        "name": "Work Shift", 
        "image": "goal_goalJobQuota-000000", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "workShiftGoal", 
        "key": "workShiftButton", 
        "managerKey": "city", 
        "description": [
            "Requires work.", 
            "Makes cash."
        ]
    }, 
    "findCleanJobMenu": {
        "image": "goal_goalCleaner-000000", 
        "completeText": "There are worse ways to survive", 
        "name": "COMPLETED - Become Cleaner", 
        "key": "findCleanJobMenu"
    }, 
    "dateTower_personDConstruction": {
        "key": "dateTower_personDConstruction", 
        "constructionIdentifier": "dateTower_personD", 
        "name": "Constructing - Date", 
        "image": "trait-aaaaaa99_tower_towerDate", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Date", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "papersGoal": {
        "currencyEntities": "fearFlash", 
        "randomPosition": 0, 
        "name": "Deliver Papers", 
        "aliveEntities": "programming", 
        "image": "trait-ede9e6_goal_goalDeliverPapers-000000", 
        "key": "papersGoal", 
        "startEntityKey": "motherland", 
        "currencyRequested": {
            "travel": 3
        }, 
        "goalMenuKey": "papersMenu", 
        "description": "A small blow for the homeland, but a blow nonetheless."
    }, 
    "thought": {
        "name": "thought", 
        "key": "thought", 
        "tags": [
            "thought"
        ]
    }, 
    "person": {
        "focusPanelKey": "focusPanel2", 
        "playerKillable": "true"
    }, 
    "cheer": {
        "name": "cheer", 
        "key": "cheer", 
        "tags": [
            "cheer"
        ]
    }, 
    "contact": {
        "name": "contact", 
        "key": "contact", 
        "tags": [
            "contact"
        ]
    }, 
    "cityPathAdult": {
        "startKey": "city", 
        "endKey": "adult", 
        "key": "cityPathAdult"
    }, 
    "tutorialTextAdulthood": {
        "nextEntities": "tutorialTextAdultPanel", 
        "text": "Now try this trait", 
        "yOffset": 100, 
        "entityToMark": "adult_ext", 
        "key": "tutorialTextAdulthood", 
        "xOffset": 300
    }, 
    "meetPersonGoalConstruction": {
        "key": "meetPersonGoalConstruction", 
        "constructionIdentifier": "meetPersonGoal", 
        "name": "Constructing - Meet New Person", 
        "image": "trait-aaaaaa99_goal_goalMeetNewPerson-000000", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Meet New Person", 
        "killOnTransform": 1, 
        "description": "It takes will to start something new."
    }, 
    "chore": {
        "name": "chore", 
        "key": "chore", 
        "tags": [
            "chore"
        ]
    }, 
    "damageIndicator": {
        "timeToDeath": 5
    }, 
    "dateTower_personD": {
        "name": "Date", 
        "image": "tower_towerDate", 
        "sourceTrait": "personD", 
        "key": "dateTower_personD", 
        "activeImage": "tower_towerDate_activeOutline-0099ff", 
        "description": "Converts flirt + travel -> date"
    }, 
    "buyRingButton": {
        "constructionKey": "buyRingGoalConstruction", 
        "name": "Buy Ring", 
        "image": "goal_goalBuyRing-008f90-000000", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "buyRingGoal", 
        "key": "buyRingButton", 
        "description": "Requires cash."
    }, 
    "dateTower_personB": {
        "name": "Date", 
        "image": "tower_towerDate", 
        "sourceTrait": "personB", 
        "key": "dateTower_personB", 
        "activeImage": "tower_towerDate_activeOutline-0099ff", 
        "description": "Converts flirt + travel -> date"
    }, 
    "cleanChoreStartCheck": {
        "count": 2, 
        "entitiesToMake": "cleanChore", 
        "notificationChecked": "goalComplete", 
        "notificationArgs": {
            "key": "cleanAction"
        }, 
        "key": "cleanChoreStartCheck"
    }, 
    "cabCompeteCheck": {
        "count": 6, 
        "entitiesToMake": "cabCompete", 
        "notificationChecked": "goalComplete", 
        "notificationArgs": {
            "key": "cabAction"
        }, 
        "key": "cabCompeteCheck"
    }, 
    "workChoreGoal": {
        "key": "workChoreGoal", 
        "randomPosition": 1, 
        "name": "Work Chores", 
        "image": "trait-ede9e6_goal_goalCabChore-000000", 
        "startEntityKey": "city", 
        "currencyRequested": {
            "travel": 3
        }, 
        "goalMenuKey": "workChoreMenu"
    }, 
    "findCleanJobButton": {
        "constructionKey": "findCleanJobGoalConstruction", 
        "name": "Become Cleaner", 
        "image": "goal_goalCleaner-000000", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "findCleanJobGoal", 
        "key": "findCleanJobButton", 
        "managerKey": "city", 
        "description": "Requires travel."
    }, 
    "painMine": {
        "image": "tower_minePain-000000", 
        "resourceKeys": [
            "pain"
        ], 
        "name": "Pain Mine", 
        "key": "painMine", 
        "activeImage": "tower_minePain-000000_activeOutline-0099ff"
    }, 
    "disavowalMenu": {
        "image": "trait-ede9e6_goal_goalDisavowal-000000", 
        "name": "COMPLETED - Disavow Homeland", 
        "key": "disavowalMenu"
    }, 
    "intelligent_ext": {
        "image": [
            "trait-ffd700", 
            "trait-E5C100", 
            "trait-FFEC00"
        ], 
        "name": "Intelligent", 
        "key": "intelligent_ext", 
        "sourceKey": "intelligent"
    }, 
    "cleanButton": {
        "constructionKey": "cleanTowerConstruction", 
        "name": "Clean", 
        "limitingKeys": [
            "cleanTower", 
            "cleanTowerConstruction"
        ], 
        "image": "tower_towerClean", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "cleanTower", 
        "subPanel": "Towers", 
        "maxTowerCount": 1, 
        "key": "cleanButton", 
        "managerKey": "city", 
        "description": "Converts chore + industry -> work"
    }, 
    "dateComplete": {
        "goodEntities": {
            "buyRingGoal": 4
        }, 
        "badEntities": "dateFail", 
        "key": "dateComplete"
    }, 
    "disavowalGoal": {
        "currencyEntities": "defineSelf", 
        "randomPosition": 0, 
        "name": "Disavow Homeland", 
        "aliveEntities": "betrayal", 
        "image": "trait-ede9e6_goal_goalDisavowal-000000", 
        "key": "disavowalGoal", 
        "currencyRequested": {
            "disavowal": 5
        }, 
        "entitiesToMake": [
            "planEscapeGoal", 
            "killMotherland"
        ], 
        "goalMenuKey": "disavowalMenu"
    }, 
    "selfControlMenu": {
        "image": "trait-ede9e6_goal_goalChat-008f90-000000", 
        "name": "COMPLETED - Don't Say Stupid Thing", 
        "key": "selfControlMenu"
    }, 
    "clickText": {
        "touchable": "true", 
        "timeToDeath": 30, 
        "key": "clickText"
    }, 
    "joyMap": {
        "startManagedEntities": [
            "cheerMine"
        ], 
        "emotionToMap": "joy", 
        "emotionMappedEntities": [
            "cheerMineButton", 
            "rechargeButton"
        ], 
        "key": "joyMap"
    }, 
    "flashback_angerConstruction": {
        "key": "flashback_angerConstruction", 
        "constructionIdentifier": "flashback_anger", 
        "name": "Flashback", 
        "image": "tower_flashback-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Flashback"
    }, 
    "findCleanJobGoalConstruction": {
        "key": "findCleanJobGoalConstruction", 
        "constructionIdentifier": "findCleanJobGoal", 
        "name": "Constructing - Become Cleaner", 
        "image": "trait-aaaaaa99_goal_goalCleaner-000000", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Become Cleaner", 
        "killOnTransform": 1, 
        "description": "It takes will to start something new."
    }, 
    "dateGoodMine": {
        "resourceCount": 1, 
        "name": "Date Going Well", 
        "image": "tower_memoryDateGood", 
        "key": "dateGoodMine", 
        "emotionTags": [
            "self", 
            "joy"
        ], 
        "resourceKeys": [
            "joy"
        ], 
        "activeImage": "tower_memoryDateGood_activeOutline-0099ff", 
        "description": "makes joy"
    }, 
    "cheerMineConstruction": {
        "key": "cheerMineConstruction", 
        "constructionIdentifier": "cheerMine", 
        "name": "Constructing - Cheer Mine", 
        "image": "trait-aaaaaa99_tower_mineCheer", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Cheer Mine", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "painMineConstruction": {
        "key": "painMineConstruction", 
        "constructionIdentifier": "painMine", 
        "name": "Constructing - Pain Mine", 
        "image": "trait-aaaaaa99_tower_minePain-000000", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Pain Mine", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "thoughtMineConstruction": {
        "key": "thoughtMineConstruction", 
        "constructionIdentifier": "thoughtMine", 
        "name": "Constructing - Thought Mine", 
        "image": "trait-aaaaaa99_tower_mineThought", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Thought Mine", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "impressMineButton_personB": {
        "constructionKey": "impressMine_personBConstruction", 
        "name": "Desire To Impress", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_mineImpress", 
        "currenciesMade": [
            "desire"
        ], 
        "entityKey": "impressMine_personB", 
        "limitingKeys": [
            "impressMine_personB", 
            "impressMine_personBConstruction"
        ], 
        "key": "impressMineButton_personB", 
        "managerKey": "personB", 
        "description": "makes desire"
    }, 
    "consequenceSadMineConstruction": {
        "key": "consequenceSadMineConstruction", 
        "constructionIdentifier": "consequenceSadMine", 
        "name": "Consequences", 
        "image": "tower_memoryConsequence-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Consequences"
    }, 
    "desire": {
        "name": "desire", 
        "key": "desire", 
        "tags": [
            "desire"
        ]
    }, 
    "acceptFearMineConstruction": {
        "key": "acceptFearMineConstruction", 
        "constructionIdentifier": "acceptFearMine", 
        "name": "Will this work?", 
        "image": "tower_memoryAcceptFear-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Will this work?"
    }, 
    "happyTraitMaker": {
        "traitMakerEmotion": "joy", 
        "traitsToMake": [
            "excitable", 
            "outgoing"
        ], 
        "key": "happyTraitMaker"
    }, 
    "workShiftGoalConstruction": {
        "key": "workShiftGoalConstruction", 
        "constructionIdentifier": "workShiftGoal", 
        "name": "Constructing - Work Shift", 
        "image": "trait-aaaaaa99_goal_goalJobQuota-000000", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Work Shift", 
        "killOnTransform": 1, 
        "description": "It takes will to start something new."
    }, 
    "openMindMenu": {
        "image": "trait-ede9e6_goal_openMind-398a00-000000", 
        "name": "COMPLETED - Moment To Breathe", 
        "key": "openMindMenu"
    }, 
    "steamEscape": {
        "steamAchievementKey": "escape", 
        "keyToCheck": "escapeGoal", 
        "key": "steamEscape"
    }, 
    "tutorialTextWakeTrain": {
        "nextEntities": "tutorialTextEnergy", 
        "text": "Now set up this train", 
        "yOffset": 220, 
        "entityToMark": "wakeGoal", 
        "key": "tutorialTextWakeTrain", 
        "xOffset": 435
    }, 
    "date_personD": {
        "person": "personD", 
        "name": "date", 
        "key": "date_personD", 
        "tags": [
            "date"
        ]
    }, 
    "tutorialFinish": {
        "text": [
            "And unpause again.", 
            "Have fun!"
        ], 
        "yStart": 600, 
        "key": "tutorialFinish"
    }, 
    "homeThoughtMineConstruction": {
        "key": "homeThoughtMineConstruction", 
        "constructionIdentifier": "homeThoughtMine", 
        "name": "Constructing - Thoughts of Home", 
        "image": "trait-aaaaaa99_tower_mineThoughtsOfHome", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Thoughts of Home", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "personDPathCity": {
        "startKey": "personD", 
        "endKey": "city", 
        "key": "personDPathCity"
    }, 
    "spyTower": {
        "image": "tower_towerSpy", 
        "activeImage": "tower_towerSpy_activeOutline-0099ff", 
        "name": "Spy", 
        "key": "spyTower", 
        "description": "Converts travel + preparation -> spy"
    }, 
    "memory_ringExcite": {
        "image": "trait-e6e617", 
        "memoryEmotions": [
            "joy"
        ], 
        "key": "memory_ringExcite", 
        "name": "Excitement"
    }, 
    "disableMemories": {
        "timeToDeath": 150, 
        "key": "disableMemories"
    }, 
    "sadTraitMaker": {
        "traitMakerEmotion": "sadness", 
        "traitsToMake": [
            "lethargy", 
            "resilient", 
            "empathic"
        ], 
        "key": "sadTraitMaker"
    }, 
    "tutorialBuildPaint2": {
        "text": "It will calm all emotions within a single tile radius", 
        "yOffset": 360, 
        "entityToMark": "adult", 
        "key": "tutorialBuildPaint2", 
        "shouldDrawArrow": 0, 
        "xOffset": 710
    }, 
    "understandMemoryMenu": {
        "image": "goal_chess-000000", 
        "name": "COMPLETED - Understand", 
        "key": "understandMemoryMenu"
    }, 
    "creep": {
        "planeOfExistence": "path"
    }, 
    "memory_defineSelf": {
        "image": "trait-aa1717", 
        "memoryEmotions": [
            "anger"
        ], 
        "key": "memory_defineSelf", 
        "name": "Define Yourself"
    }, 
    "supportMenu": {
        "image": "trait-ede9e6_goal_goalSupport-008f90-000000", 
        "name": "COMPLETED - Support", 
        "key": "supportMenu"
    }, 
    "contactTower_personDConstruction": {
        "key": "contactTower_personDConstruction", 
        "constructionIdentifier": "contactTower_personD", 
        "name": "Constructing - Contact", 
        "image": "trait-aaaaaa99_tower_towerContact", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Contact", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "travelAction": {
        "currenciesRequired": [
            "energy"
        ], 
        "currenciesToMake": [
            "travel"
        ], 
        "towerKey": "travelTower", 
        "name": "Travel", 
        "key": "travelAction"
    }, 
    "thinkAboutTower": {
        "image": "tower_towerThinkAbout", 
        "activeImage": "tower_towerThinkAbout_activeOutline-0099ff", 
        "name": "Think About", 
        "key": "thinkAboutTower", 
        "description": "Converts thought -> sympathy"
    }, 
    "newTraitParticle": {
        "effectRadius": 30, 
        "image": "dot-ffd700", 
        "key": "newTraitParticle"
    }, 
    "chessTowerConstruction": {
        "key": "chessTowerConstruction", 
        "constructionIdentifier": "chessTower", 
        "name": "Constructing - Play Chess", 
        "image": "trait-aaaaaa99_tower_towerChess", 
        "currencyRequested": {
            "thought": 5
        }, 
        "constructionName": "Play Chess", 
        "killOnTransform": 0
    }, 
    "cheerMine": {
        "image": "tower_mineCheer", 
        "resourceKeys": [
            "cheer"
        ], 
        "name": "Cheer Mine", 
        "key": "cheerMine", 
        "activeImage": "tower_mineCheer_activeOutline-0099ff"
    }, 
    "difficultySetting": {
        "key": "difficultySetting"
    }, 
    "gainControl_anger": {
        "barColor": "cc9999", 
        "text": "You're just too angry to think right now.", 
        "textColor": 660000, 
        "key": "gainControl_anger"
    }, 
    "betrayal": {
        "emotionsToAdd": {
            "anger": 2
        }, 
        "name": "Betrayal", 
        "image": "tower_memoryDefineSelf-000000", 
        "key": "betrayal", 
        "memoryKey": "memory_betrayal", 
        "emotionTags": "self", 
        "resourceKey": "betrayalMineConstruction", 
        "activeImage": "tower_memoryDefineSelf-000000_activeOutline-0099ff", 
        "description": "They asked too much"
    }, 
    "buyEscapeGoal": {
        "key": "buyEscapeGoal", 
        "randomPosition": 0, 
        "name": "Prepare For Escape", 
        "image": "trait-ede9e6_goal_goalEscape-000000", 
        "currencyRequested": {
            "cash": 5
        }, 
        "entitiesToMake": [
            "escapeGoal"
        ], 
        "goalMenuKey": "buyEscapeMenu"
    }, 
    "planTower": {
        "image": "tower_towerPlan", 
        "activeImage": "tower_towerPlan_activeOutline-0099ff", 
        "name": "Plan", 
        "key": "planTower", 
        "description": "Converts thought -> preparation"
    }, 
    "flareMemoryGoal": {
        "key": "flareMemoryGoal", 
        "randomPosition": 0, 
        "currencyRequested": {
            "thought": 3
        }, 
        "image": "goal_chess-000000", 
        "flareMemory": 1, 
        "goalMenuKey": "flareMemoryMenu", 
        "name": "Flare"
    }, 
    "fantasyMine_personD": {
        "name": "Fantasize About", 
        "image": "tower_mineFantasy", 
        "sourceTrait": "personD", 
        "key": "fantasyMine_personD", 
        "resourceKeys": [
            "energy"
        ], 
        "activeImage": "tower_mineFantasy_activeOutline-0099ff"
    }, 
    "learnFirstMissionGoal": {
        "currencyEntities": "fearFlash", 
        "randomPosition": 1, 
        "name": "Learn First Mission", 
        "aliveEntities": "programming", 
        "image": "trait-ede9e6_goal_goalLearnFirstMission-000000", 
        "key": "learnFirstMissionGoal", 
        "startEntityKey": "motherland", 
        "currencyRequested": {
            "contact": 2
        }, 
        "entitiesToMake": [
            "papersGoal"
        ], 
        "goalMenuKey": "learnFirstMissionMenu", 
        "description": [
            "This country never spared itself in hurting you.", 
            "Should you hold back? Do you dare to?"
        ]
    }, 
    "fantasyMine_personB": {
        "name": "Fantasize About", 
        "image": "tower_mineFantasy", 
        "sourceTrait": "personB", 
        "key": "fantasyMine_personB", 
        "resourceKeys": [
            "energy"
        ], 
        "activeImage": "tower_mineFantasy_activeOutline-0099ff"
    }, 
    "empathizeButton": {
        "constructionKey": "empathizeTowerConstruction", 
        "name": "Empathize", 
        "image": "tower_social", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "empathizeTower", 
        "limitingKeys": [
            "empathizeTower", 
            "empathizeTowerConstruction"
        ], 
        "key": "empathizeButton", 
        "description": "Converts pain -> sympathy"
    }, 
    "memory_fearFlash": {
        "image": "trait-7533f2", 
        "memoryEmotions": [
            "fear"
        ], 
        "key": "memory_fearFlash", 
        "name": "Fear Flash"
    }, 
    "dateFailMineConstruction": {
        "key": "dateFailMineConstruction", 
        "constructionIdentifier": "dateFailMine", 
        "name": "Date Wasn't Perfect", 
        "image": "tower_memoryDateFail-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Date Wasn't Perfect"
    }, 
    "confessFearMine": {
        "resourceCount": 2, 
        "name": "Fear of Confession", 
        "image": "tower_memoryConfession-000000", 
        "key": "confessFearMine", 
        "emotionTags": [
            "self", 
            "fear"
        ], 
        "resourceKeys": [
            "fear"
        ], 
        "activeImage": "tower_memoryConfession-000000_activeOutline-0099ff", 
        "description": "Will I be forgiven? - makes fear"
    }, 
    "lifeThreat": {
        "emotionsToAdd": {
            "fear": 3
        }, 
        "name": "Fear for Life", 
        "image": "tower_memoryThreat-000000", 
        "key": "lifeThreat", 
        "memoryKey": "memory_lifeThreat", 
        "emotionTags": "self", 
        "resourceKey": "lifeThreatMineConstruction", 
        "activeImage": "tower_memoryThreat-000000_activeOutline-0099ff", 
        "description": "These are serious people"
    }, 
    "personCurrency": {
        "planeOfExistence": "path"
    }, 
    "industry": {
        "name": "industry", 
        "key": "industry", 
        "tags": [
            "industry"
        ]
    }, 
    "cleanCompeteCheck": {
        "count": 6, 
        "entitiesToMake": "cleanCompete", 
        "notificationChecked": "goalComplete", 
        "notificationArgs": {
            "key": "cleanAction"
        }, 
        "key": "cleanCompeteCheck"
    }, 
    "cabTowerConstruction": {
        "key": "cabTowerConstruction", 
        "constructionIdentifier": "cabTower", 
        "name": "Constructing - Drive", 
        "image": "trait-aaaaaa99_tower_towerCab", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Drive", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "knownAgent": {
        "buttonKeys": [
            "contactButton"
        ], 
        "name": "known agent", 
        "key": "knownAgent"
    }, 
    "dateButton_personB": {
        "constructionKey": "dateTower_personBConstruction", 
        "name": "Date", 
        "image": "tower_towerDate", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "dateTower_personB", 
        "personHeld": "personB", 
        "managerKey": "personB", 
        "limitingKeys": [
            "dateTower_personB", 
            "dateTower_personBConstruction"
        ], 
        "key": "dateButton_personB", 
        "subPanel": "Towers", 
        "description": "Converts flirt + travel -> date"
    }, 
    "buyTimeGoal": {
        "key": "buyTimeGoal", 
        "randomPosition": 0, 
        "name": "Buy Time", 
        "image": "goal_goalBuyTime-008f90-000000", 
        "currencyRequested": {
            "social": 5
        }, 
        "goalMenuKey": "buyTimeMenu"
    }, 
    "askOutMenu": {
        "image": "goal_goalAskOut-008f90-000000", 
        "completeText": "You have a date!", 
        "name": "COMPLETED - Ask For Date", 
        "key": "askOutMenu"
    }, 
    "cleanTower": {
        "image": "tower_towerClean", 
        "activeImage": "tower_towerClean_activeOutline-0099ff", 
        "name": "Clean", 
        "key": "cleanTower", 
        "description": "Converts chore + industry -> work"
    }, 
    "motherland": {
        "sector": [
            1, 
            0
        ], 
        "name": "Homeland", 
        "image": "trait-00ff99", 
        "isaacEntities": [
            "industryMineButton", 
            "homeThoughtMineButton"
        ], 
        "key": "motherland", 
        "startManagedEntities": [
            "motherlandPathAdult"
        ], 
        "description": "Takes more than moving to break these ties"
    }, 
    "fantasyMineButton_personD": {
        "constructionKey": "fantasyMine_personDConstruction", 
        "name": "Fantasize About", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_mineFantasy", 
        "currenciesMade": [
            "energy"
        ], 
        "entityKey": "fantasyMine_personD", 
        "limitingKeys": [
            "fantasyMine_personD", 
            "fantasyMine_personDConstruction"
        ], 
        "key": "fantasyMineButton_personD", 
        "managerKey": "personD", 
        "description": "makes energy"
    }, 
    "contactTower": {
        "image": "tower_towerContact", 
        "activeImage": "tower_towerContact_activeOutline-0099ff", 
        "name": "Contact", 
        "key": "contactTower", 
        "description": "Converts travel -> contact"
    }, 
    "askOutButton": {
        "constructionKey": "askOutGoalConstruction", 
        "name": "Ask For Date", 
        "image": "goal_goalAskOut-008f90-000000", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "askOutGoal", 
        "disablingEntities": [
            "restaurantPrepGoal", 
            "restaurantDateGoal", 
            "parkPrepGoal", 
            "parkDateGoal", 
            "walkDateGoal"
        ], 
        "key": "askOutButton", 
        "description": "Requires flirt."
    }, 
    "buyTimeButton": {
        "constructionKey": "buyTimeGoalConstruction", 
        "name": "Buy Time", 
        "image": "goal_goalBuyTime-008f90-000000", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "buyTimeGoal", 
        "key": "buyTimeButton", 
        "description": "Requires social."
    }, 
    "dateButton_personD": {
        "constructionKey": "dateTower_personDConstruction", 
        "name": "Date", 
        "image": "tower_towerDate", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "dateTower_personD", 
        "personHeld": "personD", 
        "managerKey": "personD", 
        "limitingKeys": [
            "dateTower_personD", 
            "dateTower_personDConstruction"
        ], 
        "key": "dateButton_personD", 
        "subPanel": "Towers", 
        "description": "Converts flirt + travel -> date"
    }, 
    "memory_dateLucky": {
        "image": "trait-e6e617", 
        "memoryEmotions": [
            "joy"
        ], 
        "key": "memory_dateLucky", 
        "name": "Feeling Lucky"
    }, 
    "understandMemoryGoalConstruction": {
        "key": "understandMemoryGoalConstruction", 
        "constructionIdentifier": "understandMemoryGoal", 
        "name": "Constructing - Understand", 
        "image": "trait-aaaaaa99_chess", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Understand", 
        "killOnTransform": 1, 
        "description": "It takes will to start something new."
    }, 
    "missHomeMineConstruction": {
        "key": "missHomeMineConstruction", 
        "constructionIdentifier": "missHomeMine", 
        "name": "Missing Home", 
        "image": "tower_memoryMissHome-000000-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Missing Home"
    }, 
    "dailyHelpInfoPanel": {
        "components": [
            "dailyEntityMakerComponent"
        ], 
        "helpLines": [
            "These are the sources of the recurring goals in your life."
        ], 
        "name": "Recurring Chores", 
        "key": "dailyHelpInfoPanel"
    }, 
    "dateGreatMine": {
        "resourceCount": 3, 
        "name": "Date Was Great", 
        "image": "tower_memoryDateGood", 
        "key": "dateGreatMine", 
        "emotionTags": [
            "self", 
            "joy"
        ], 
        "resourceKeys": [
            "joy"
        ], 
        "activeImage": "tower_memoryDateGood_activeOutline-0099ff", 
        "description": "makes joy"
    }, 
    "cabAction": {
        "currenciesRequired": [
            "travel", 
            "industry"
        ], 
        "currenciesToMake": [
            "work"
        ], 
        "towerKey": "cabTower", 
        "name": "Drive", 
        "key": "cabAction"
    }, 
    "askAboutGoal": {
        "key": "askAboutGoal", 
        "randomPosition": 0, 
        "name": "Ask About", 
        "image": "trait-ede9e6_goal_goalAskAbout-008f90-000000", 
        "currencyRequested": {
            "social": 3
        }, 
        "entitiesToMake": "learnAboutGoal", 
        "goalMenuKey": "askAboutMenu", 
        "description": "Hurts nothing to ask if you are okay."
    }, 
    "towerTickModifier": {
        "timeToDeath": 15
    }, 
    "makeTarget": {
        "opinionKey": "target", 
        "personKey": "TargetA", 
        "key": "makeTarget"
    }, 
    "frustration": {
        "name": "frustration", 
        "key": "frustration", 
        "tags": [
            "frustration"
        ]
    }, 
    "traitHelpInfoPanel": {
        "components": [
            "entityManagerComponent"
        ], 
        "helpLines": [
            "Traits are the things that define you as a person.", 
            "They range from the city you live in to your maturity to the people you know.", 
            "They grow with use and you can build towers on them."
        ], 
        "name": "Traits", 
        "key": "traitHelpInfoPanel"
    }, 
    "travelButton": {
        "constructionKey": "travelTowerConstruction", 
        "name": "Travel", 
        "image": "tower_towerTravel", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "travelTower", 
        "limitingKeys": [
            "travelTower", 
            "travelTowerConstruction"
        ], 
        "key": "travelButton", 
        "subPanel": "Towers", 
        "description": "Converts energy -> travel"
    }, 
    "chapter5Menu": {
        "image": "activeHex", 
        "completeText": "Things are becoming serious.", 
        "name": "Chapter 5 - Escalation", 
        "key": "chapter5Menu"
    }, 
    "missHome": {
        "emotionsToAdd": {
            "sadness": 1
        }, 
        "name": "Missing Home", 
        "image": "tower_memoryMissHome-000000", 
        "key": "missHome", 
        "memoryKey": "memory_missHome", 
        "emotionTags": "self", 
        "resourceKey": "missHomeMineConstruction", 
        "activeImage": "tower_memoryMissHome-000000_activeOutline-0099ff"
    }, 
    "cabButton": {
        "constructionKey": "cabTowerConstruction", 
        "name": "Drive", 
        "limitingKeys": [
            "cabTower", 
            "cabTowerConstruction"
        ], 
        "image": "tower_towerCab", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "cabTower", 
        "subPanel": "Towers", 
        "maxTowerCount": 1, 
        "key": "cabButton", 
        "managerKey": "city", 
        "description": "Converts travel + industry -> work"
    }, 
    "painMineButton": {
        "constructionKey": "painMineConstruction", 
        "name": "Pain Mine", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_minePain-000000", 
        "currenciesMade": [
            "pain"
        ], 
        "entityKey": "painMine", 
        "limitingKeys": [
            "painMine", 
            "painMineConstruction"
        ], 
        "key": "painMineButton", 
        "description": "makes pain"
    }, 
    "trainPathHelpInfoPanel": {
        "components": [
            "trainPathComponent"
        ], 
        "helpLines": [
            "These are the trains of thought that let you complete tasks by moving things from one building to another."
        ], 
        "name": "Trains", 
        "key": "trainPathHelpInfoPanel"
    }, 
    "dateGood": {
        "emotionsToAdd": {
            "joy": 1
        }, 
        "name": "Date Going Well", 
        "image": "tower_memoryDateGood", 
        "key": "dateGood", 
        "memoryKey": "memory_dateGood", 
        "emotionTags": "self", 
        "resourceKey": "dateGoodMineConstruction", 
        "activeImage": "tower_memoryDateGood_activeOutline-0099ff"
    }, 
    "dateGoodMineConstruction": {
        "key": "dateGoodMineConstruction", 
        "constructionIdentifier": "dateGoodMine", 
        "name": "Date Going Well", 
        "image": "tower_memoryDateGood-666666aa", 
        "currencyRequested": {
            "impulse": 1
        }, 
        "constructionName": "Date Going Well"
    }, 
    "desireMineConstruction": {
        "key": "desireMineConstruction", 
        "constructionIdentifier": "desireMine", 
        "name": "Constructing - Aspiration", 
        "image": "trait-aaaaaa99_tower_trophy", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Aspiration", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "willMine": {
        "name": "Will Mine", 
        "image": "tower_mineWill", 
        "sourceTrait": "adult", 
        "key": "willMine", 
        "resourceKeys": [
            "will"
        ], 
        "activeImage": "tower_mineWill_activeOutline-0099ff"
    }, 
    "sadness": {
        "emotion": "sadness", 
        "name": "sadness", 
        "image": "creep-0000ff", 
        "damage": 0, 
        "health": 10, 
        "key": "sadness"
    }, 
    "cabPrideCheck": {
        "count": 3, 
        "entitiesToMake": "cabPride", 
        "notificationChecked": "goalComplete", 
        "notificationArgs": {
            "key": "cabAction"
        }, 
        "key": "cabPrideCheck"
    }, 
    "understandButton": {
        "constructionKey": "understandTowerConstruction", 
        "name": "Understand", 
        "image": "tower_understand", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "understandTower", 
        "subPanel": "Towers", 
        "limitingKeys": [
            "understandTower", 
            "understandTowerConstruction"
        ], 
        "key": "understandButton", 
        "managerKey": "intelligent", 
        "description": "Converts thought -> understand"
    }, 
    "findCabJobMenu": {
        "image": "goal_goalCabbie-000000", 
        "completeText": "There are worse ways to survive", 
        "name": "COMPLETED - Become Cabbie", 
        "key": "findCabJobMenu"
    }, 
    "daydreamMineButton_personD": {
        "constructionKey": "daydreamMine_personDConstruction", 
        "name": "Daydream", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_daydream", 
        "currenciesMade": [
            "energy"
        ], 
        "entityKey": "daydreamMine_personD", 
        "limitingKeys": [
            "daydreamMine_personD", 
            "daydreamMine_personDConstruction"
        ], 
        "key": "daydreamMineButton_personD", 
        "managerKey": "personD", 
        "description": "makes energy"
    }, 
    "findCabJobGoal": {
        "currencyEntities": "nastyComment", 
        "randomPosition": 0, 
        "name": "Become Cabbie", 
        "image": "goal_goalCabbie-000000", 
        "key": "findCabJobGoal", 
        "startEntityKey": "city", 
        "currencyRequested": {
            "travel": 3
        }, 
        "entitiesToMake": [
            "cabButton", 
            "job", 
            "workShiftButton", 
            "killFindJob"
        ], 
        "goalMenuKey": "findCabJobMenu", 
        "description": "No one likes foreigners, but some people will employ them."
    }, 
    "memory_acceptFear": {
        "image": "trait-7533f2", 
        "memoryEmotions": [
            "fear"
        ], 
        "key": "memory_acceptFear", 
        "name": "Will this work?"
    }, 
    "daydreamMineButton_personB": {
        "constructionKey": "daydreamMine_personBConstruction", 
        "name": "Daydream", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_daydream", 
        "currenciesMade": [
            "energy"
        ], 
        "entityKey": "daydreamMine_personB", 
        "limitingKeys": [
            "daydreamMine_personB", 
            "daydreamMine_personBConstruction"
        ], 
        "key": "daydreamMineButton_personB", 
        "managerKey": "personB", 
        "description": "makes energy"
    }, 
    "nastyCommentMine": {
        "resourceCount": 2, 
        "name": "Casual Racism", 
        "image": "tower_memoryNastyComment-000000", 
        "key": "nastyCommentMine", 
        "emotionTags": [
            "self", 
            "anger"
        ], 
        "resourceKeys": [
            "anger"
        ], 
        "activeImage": "tower_memoryNastyComment-000000_activeOutline-0099ff", 
        "description": "makes anger"
    }, 
    "findCabJobButton": {
        "constructionKey": "findCabJobGoalConstruction", 
        "name": "Become Cabbie", 
        "image": "goal_goalCabbie-000000", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "findCabJobGoal", 
        "key": "findCabJobButton", 
        "managerKey": "city", 
        "description": "Requires travel."
    }, 
    "askOutGoalConstruction": {
        "key": "askOutGoalConstruction", 
        "constructionIdentifier": "askOutGoal", 
        "name": "Constructing - Ask For Date", 
        "image": "trait-aaaaaa99_goal_goalAskOut-008f90-000000", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Ask For Date", 
        "killOnTransform": 1, 
        "description": "It takes will to start something new."
    }, 
    "contactTowerConstruction": {
        "key": "contactTowerConstruction", 
        "constructionIdentifier": "contactTower", 
        "name": "Constructing - Contact", 
        "image": "trait-aaaaaa99_tower_towerContact", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Contact", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "tutorialPlaceEnergy": {
        "nextEntities": [
            "tutorialTextEnergyPanel", 
            "tutorialTextEnergyPanel2"
        ], 
        "text": "Now put it on any of the highlighted hexes.", 
        "yOffset": 100, 
        "entityToMark": "adult_ext", 
        "key": "tutorialPlaceEnergy", 
        "xOffset": 200
    }, 
    "confessAction_personD": {
        "towerKey": "confessTower_personD", 
        "name": "confess - The One You Admire", 
        "entitiesOnConvert": "confessFear", 
        "currenciesRequired": [
            "sin"
        ], 
        "currenciesToMake": [
            "confession"
        ], 
        "key": "confessAction_personD"
    }, 
    "supportButton": {
        "constructionKey": "supportGoalConstruction", 
        "name": "Support", 
        "image": "trait-ede9e6_goal_goalSupport-008f90-000000", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "supportGoal", 
        "key": "supportButton", 
        "description": "Requires sympathy."
    }, 
    "motherland_ext": {
        "image": [
            "trait-00ff99", 
            "trait-00E589", 
            "trait-00FFA8"
        ], 
        "name": "Homeland", 
        "key": "motherland_ext", 
        "sourceKey": "motherland"
    }, 
    "walkDateGoal": {
        "currencyEntities": {
            "dateGood": 20, 
            "dateSad": 20
        }, 
        "randomPosition": 0, 
        "name": "Walking Date", 
        "image": "goal_goalSkate-008f90-000000", 
        "description": {
            "personB": "You're just happier around this person.", 
            "personD": "Time together stimulates like with no one else"
        }, 
        "key": "walkDateGoal", 
        "currencyRequested": {
            "date": 2
        }, 
        "entitiesToMake": "dateComplete", 
        "goalMenuKey": "walkDateMenu", 
        "timeToDeath": 150
    }, 
    "sadnessResourceModifier": {
        "doubleResources": [
            "industryMine", 
            "thoughtMine", 
            "homeThoughtMine"
        ], 
        "stopResources": [
            "socialMine", 
            "fantasyMine", 
            "daydreamMine", 
            "impressMine", 
            "energyMine", 
            "cheerMine"
        ], 
        "image": "modifier-0000ff", 
        "key": "sadnessResourceModifier"
    }, 
    "energyMineConstruction": {
        "key": "energyMineConstruction", 
        "constructionIdentifier": "energyMine", 
        "name": "Constructing - Energy Spring", 
        "image": "trait-aaaaaa99_tower_energy", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Energy Spring", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "tutorialTextWakeUp": {
        "nextEntities": [
            "tutorialTextWakeUpPanel", 
            "tutorialTextWakeUpPanel2"
        ], 
        "text": "Start here", 
        "xOffset": 200, 
        "key": "tutorialTextWakeUp", 
        "entityToMark": "wakeGoal"
    }, 
    "joyTickModifier": {
        "tickFactor": 1.5, 
        "distractFactor": 10, 
        "image": "modifier-ffff00", 
        "key": "joyTickModifier"
    }, 
    "thoughtMineButton": {
        "constructionKey": "thoughtMineConstruction", 
        "name": "Thought Mine", 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_mineThought", 
        "currenciesMade": [
            "thought"
        ], 
        "entityKey": "thoughtMine", 
        "limitingKeys": [
            "thoughtMine", 
            "thoughtMineConstruction"
        ], 
        "key": "thoughtMineButton", 
        "managerKey": "intelligent", 
        "description": "makes thought"
    }, 
    "askAboutButton": {
        "constructionKey": "askAboutGoalConstruction", 
        "name": "Ask About", 
        "image": "trait-ede9e6_goal_goalAskAbout-008f90-000000", 
        "entityCost": {
            "will": 2
        }, 
        "entityKey": "askAboutGoal", 
        "key": "askAboutButton", 
        "description": "Requires social."
    }, 
    "dateFail": {
        "emotionsToAdd": {
            "sadness": 4
        }, 
        "name": "Date Wasn't Perfect", 
        "image": "tower_memoryDateFail-000000", 
        "key": "dateFail", 
        "memoryKey": "memory_dateFail", 
        "emotionTags": "self", 
        "resourceKey": "dateFailMineConstruction", 
        "activeImage": "tower_memoryDateFail-000000_activeOutline-0099ff"
    }, 
    "date_personB": {
        "person": "personB", 
        "name": "date", 
        "key": "date_personB", 
        "tags": [
            "date"
        ]
    }, 
    "chessProject": {
        "isaacEntities": [
            "chessMatchItem"
        ], 
        "name": "Chess Tournament", 
        "key": "chessProject", 
        "timeToDeath": 240
    }, 
    "spyResult": {
        "entitiesToKill": [
            "personC", 
            "personC_ext"
        ], 
        "entitiesToMake": [
            "learnSpyNewsGoal"
        ], 
        "key": "spyResult"
    }, 
    "cabChore": {
        "image": "disabledTower", 
        "dayCount": 4, 
        "name": "Cab Chore", 
        "key": "cabChore", 
        "dayStartEntity": "cabChoreGoal"
    }, 
    "talkAction_personD": {
        "currenciesRequired": [
            "social_personD"
        ], 
        "towerKey": "talkTower_personD", 
        "name": "talk - The One You Admire", 
        "key": "talkAction_personD"
    }, 
    "acquaintance": {
        "buttonKeys": [
            "socialMineButton"
        ], 
        "successOpinions": {
            "friend": 60
        }, 
        "name": "acquaintance", 
        "key": "acquaintance"
    }, 
    "talkAction_personB": {
        "currenciesRequired": [
            "social_personB"
        ], 
        "towerKey": "talkTower_personB", 
        "name": "talk - The Pretty Person", 
        "key": "talkAction_personB"
    }, 
    "impressMine_personBConstruction": {
        "key": "impressMine_personBConstruction", 
        "constructionIdentifier": "impressMine_personB", 
        "name": "Constructing - Desire To Impress", 
        "image": "trait-aaaaaa99_tower_mineImpress", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Desire To Impress", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "askOutSuccess": {
        "emotionsToAdd": {
            "joy": 3
        }, 
        "name": "You have a date", 
        "image": "tower_memoryAskOutSuccess", 
        "key": "askOutSuccess", 
        "memoryKey": "memory_askOutSuccess", 
        "emotionTags": "self", 
        "resourceKey": "askOutSuccessMineConstruction", 
        "activeImage": "tower_memoryAskOutSuccess_activeOutline-0099ff"
    }, 
    "walkTowerConstruction": {
        "key": "walkTowerConstruction", 
        "constructionIdentifier": "walkTower", 
        "name": "Constructing - Walk", 
        "image": "trait-aaaaaa99_tower_towerWalk", 
        "currencyRequested": {
            "energy": 5
        }, 
        "constructionName": "Walk", 
        "killOnTransform": 0
    }, 
    "askAboutMenu": {
        "image": "trait-ede9e6_goal_goalAskAbout-008f90-000000", 
        "name": "COMPLETED - Ask About", 
        "key": "askAboutMenu"
    }, 
    "prepareTower": {
        "image": "tower_spy", 
        "activeImage": "tower_spy_activeOutline-0099ff", 
        "name": "Prepare", 
        "key": "prepareTower", 
        "description": "Converts caution -> preparation"
    }, 
    "industryMineButton": {
        "constructionKey": "industryMineConstruction", 
        "name": "Work Ethic", 
        "limitingKeys": [
            "industryMine", 
            "industryMineConstruction"
        ], 
        "entityCost": {
            "will": 2
        }, 
        "image": "tower_mineIndustrious", 
        "currenciesMade": [
            "industry"
        ], 
        "entityKey": "industryMine", 
        "maxTowerCount": 1, 
        "key": "industryMineButton", 
        "managerKey": "motherland", 
        "description": "makes industry"
    }, 
    "spyTowerConstruction": {
        "key": "spyTowerConstruction", 
        "constructionIdentifier": "spyTower", 
        "name": "Constructing - Spy", 
        "image": "trait-aaaaaa99_tower_towerSpy", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Spy", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "spyAction": {
        "towerKey": "spyTower", 
        "name": "spy", 
        "entitiesOnConvert": "fearFlash", 
        "currenciesRequired": [
            "travel", 
            "preparation"
        ], 
        "currenciesToMake": [
            "spy"
        ], 
        "key": "spyAction"
    }, 
    "willMineConstruction": {
        "key": "willMineConstruction", 
        "constructionIdentifier": "willMine", 
        "name": "Constructing - Will Mine", 
        "image": "trait-aaaaaa99_tower_mineWill", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Will Mine", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "confessTower_personDConstruction": {
        "key": "confessTower_personDConstruction", 
        "constructionIdentifier": "confessTower_personD", 
        "name": "Constructing - Confess To", 
        "image": "trait-aaaaaa99_tower_towerConfess", 
        "currencyRequested": {
            "will": 2
        }, 
        "constructionName": "Confess To", 
        "killOnTransform": 0, 
        "description": "It takes will to start something new."
    }, 
    "tutorialTextWakeUpFinish": {
        "nextEntities": "tutorialTextWakeTrain", 
        "text": "Now, go back to the goal.", 
        "xOffset": 200, 
        "key": "tutorialTextWakeUpFinish", 
        "entityToMark": "wakeGoal"
    }, 
    "cautionMine": {
        "image": "tower_mineCaution", 
        "resourceKeys": [
            "caution"
        ], 
        "name": "Caution Mine", 
        "key": "cautionMine", 
        "activeImage": "tower_mineCaution_activeOutline-0099ff"
    }
};

var ecs = {
    memoryComponent: function(args){
        if (args.memoryEmotions === undefined){ throw "ECS fail " + args.key + " missing memoryEmotions"; }
        if (args.sourceKey === undefined){ throw "ECS fail " + args.key + " missing sourceKey"; }
        if (args.source2Key === undefined){ throw "ECS fail " + args.key + " missing source2Key"; }
        return {
            coreMemory: false,
            source2Key: args['source2Key'],
            ticksToCoreMemory: 180,
            flashbackFunction: flashbackFunction,
            sourceKey: args['sourceKey'],
            flashbackCount: 1,
            memoryEmotions: args['memoryEmotions']
        };
    },
    memory_confessFear: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_confessFear)));
        return ecs.memory(args);
    },
    randomCarousel: function(args){
        return {
            animationTime: 3,
            flatDisplayStartTime: undefined,
            carousel: undefined,
            render: randomCarouselRender,
            goodImage: "randomGoodImage",
            frameImage: "frame",
            badImage: "randomBadImage",
            goodResult: undefined,
            setMarker: randomCarouselSetMarker,
            imgSize: [100, 100],
            animationDone: false
        };
    },
    gameStageNotificationComponent: function(args){
        return {
            notificationFunctions: {goalComplete: checkStageComplete},
            serializeComponent: ecs.notificationSerializeComponent(args)
        };
    },
    singleUseTrainPathComponent: function(args){
        return {
            makeTrain: singleUseMakeTrain,
            trainId: undefined,
            trainStops: args['trainStops'],
            killTrainPath: undefined,
            stoppable: false,
            trainKey: PARAMETERS.carryTrain,
            canMakeTrain: canMakeTrain,
            requiresFullPath: (args["requiresFullPath"] === undefined) ? true : args["requiresFullPath"],
            getKillTrainPath: getKillTrainPath,
            trainsToMake: 1,
            trainStartId: args['trainStartId'],
            paused: false,
            baseMakeTrain: makeTrain,
            allPaths: [],
            trainColor: undefined
        };
    },
    makeIfMissingExec: function(args){
        if (args.entitiesToMake === undefined){ throw "ECS fail " + args.key + " missing entitiesToMake"; }
        if (args.missingKey === undefined){ throw "ECS fail " + args.key + " missing missingKey"; }
        if (args.source === undefined){ throw "ECS fail " + args.key + " missing source"; }
        return {
            source: args['source'],
            execute: makeIfMissingExec,
            entitiesToMake: args['entitiesToMake'],
            missingKey: args['missingKey']
        };
    },
    applyTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.applyTower)));
        return ecs.memoryTower(args);
    },
    utilityEntityRenderComponent: function(args){
        return {
            insetColor: "#000000",
            fullAnimationTime: 0.1,
            yTextOffset: 5,
            backImage: "opaqueBack",
            drawImageButton: drawImageButton,
            animatingTime: 0.1,
            priority: 19,
            render: utilityEntityRender,
            drawPanelButton: drawPanelButton,
            snapPanelPos: snapPanelPos,
            showAllBuildingPanel: undefined,
            renderTime: 0,
            textColor: "#000000",
            renderState: "default",
            specificRender: args['specificRender'],
            drawHelpButton: drawHelpButton
        };
    },
    allCurrencyRenderComponent: function(args){
        return {
            priority: 13,
            image: "currency",
            renderState: "default",
            render: allCurrencyRender
        };
    },
    paramPathExec: function(args){
        if (args.startKey === undefined){ throw "ECS fail " + args.key + " missing startKey"; }
        if (args.endKey === undefined){ throw "ECS fail " + args.key + " missing endKey"; }
        return {
            startKey: args['startKey'],
            execute: paramPathExec,
            trainPathKey: PARAMETERS.trainPath,
            endKey: args['endKey']
        };
    },
    stateComponent: function(args){
        return {
            paused: false,
            playerControlled: false
        };
    },
    rentMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.rentMenu)));
        return ecs.goalCompleteMenu(args);
    },
    timedRenderComponent: function(args){
        if (args.nextRenderComponent === undefined){ throw "ECS fail " + args.key + " missing nextRenderComponent"; }
        if (args.renderWhileTimed === undefined){ throw "ECS fail " + args.key + " missing renderWhileTimed"; }
        return {
            renderWhileTimed: args['renderWhileTimed'],
            render: timedRender,
            nextRenderComponent: args['nextRenderComponent'],
            timeDrawn: 0,
            renderTime: (args["renderTime"] === undefined) ? 0.15 : args["renderTime"],
            renderState: "default",
            priority: args['priority']
        };
    },
    makeIfMissing: function(args){
        return {
            createExecutables: [ecs.makeIfMissingExec(args), ecs.killEntity(args)]
        };
    },
    bulletTickComponent: function(args){
        return {
            tick: bulletTick
        };
    },
    infiniteEmotionSourceComponent: function(args){
        return {
            spawnComplete: resetEmotionSource,
            border: "#ffff00",
            emotions: unrollDict(args.emotionsToAdd)
        };
    },
    chapter4Menu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chapter4Menu)));
        return ecs.gameStageStartMenu(args);
    },
    sourceLifeSentinelComponent: function(args){
        if (args.sourceId === undefined){ throw "ECS fail " + args.key + " missing sourceId"; }
        return {
            sourceId: args['sourceId'],
            checkAlive: checkParticleEffectAlive
        };
    },
    pathButton: function(args){
        return {
            rendererComponents: [ecs.pathButtonRenderComponent(args)]
        };
    },
    makeRandomMultiple: function(args){
        return {
            replaceMarker: ecs.makeRandomMultipleReplaceMarker(args)
        };
    },
    timedFocusHolder: function(args){
        return {
            interactionComponent: ecs.interactionComponent(args),
            focusCostComponent: ecs.focusCostComponent(args),
            identityComponent: ecs.identityComponent(args),
            timedDeathComponent: ecs.timedDeathComponent(args)
        };
    },
    nullEntity: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            createExecutables: [ecs.killEntity(args)]
        };
    },
    makeParticleEffect: function(args){
        if (args.particleKey === undefined){ throw "ECS fail " + args.key + " missing particleKey"; }
        return {
            particleKey: args['particleKey'],
            execute: makeParticleEffect
        };
    },
    creepDeathExec: function(args){
        return {
            execute: creepDeathExec,
            creepDeathKey: "creepDeath"
        };
    },
    clickKillComponent: function(args){
        return {
            onClick: clickKill
        };
    },
    currencyPath: function(args){
        return {
            currencyPathComponent: ecs.currencyPathComponent(args)
        };
    },
    willMineButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.willMineButton)));
        return ecs.resourceButton(args);
    },
    walkTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.walkTower)));
        return ecs.tower(args);
    },
    goalCompleteMenuCreateExec: function(args){
        return {
            source: (args["source"] === undefined) ? undefined : args["source"],
            execute: goalCompleteMenuCreateExec
        };
    },
    pairTransformMarker: function(args){
        if (args.trait1 === undefined){ throw "ECS fail " + args.key + " missing trait1"; }
        if (args.trait2 === undefined){ throw "ECS fail " + args.key + " missing trait2"; }
        return {
            trait2: args['trait2'],
            tempKey: "tempTrait",
            trait1: args['trait1'],
            getReplaceKeys: pairTransformGetReplaceKeys
        };
    },
    setPanelComponent: function(args){
        return {
            panelButtons: [],
            expandPanel: expandPanel,
            minimized: false,
            canClose: false,
            closed: false,
            ignoreWhenMinimized: true
        };
    },
    damageUpgradeCheck: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            createExecutables: [ecs.damamgeUpgradeCheckExec(args)]
        };
    },
    fearResourceModifier: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fearResourceModifier)));
        return ecs.resourceModifier(args);
    },
    gameEnd: function(args){
        return {
            renderComponents: [ecs.gameEndRenderComponent(args)],
            pauseComponent: ecs.pauseComponent(args),
            gameEndComponent: ecs.gameEndComponent(args)
        };
    },
    memory_dateFail: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_dateFail)));
        return ecs.memory(args);
    },
    traitMaker: function(args){
        return {
            traitMakerComponent: ecs.traitMakerComponent(args)
        };
    },
    transformTrait: function(args){
        if (args.sourceTrait === undefined){ throw "ECS fail " + args.key + " missing sourceTrait"; }
        if (args.newTrait === undefined){ throw "ECS fail " + args.key + " missing newTrait"; }
        return {
            retainHealth: (args["retainHealth"] === undefined) ? true : args["retainHealth"],
            execute: transformTrait,
            sourceTrait: args['sourceTrait'],
            newTrait: args['newTrait']
        };
    },
    thoughtMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.thoughtMine)));
        return ecs.resource(args);
    },
    understandTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.understandTower)));
        return ecs.tower(args);
    },
    memoryConstruction: function(args){
        args["touchable"] = true;
        args["canExpandInto"] = false;
        return {
            junctionComponent: ecs.junctionComponent(args),
            rendererComponents: [ecs.constructionRenderComponent(args), ecs.progressRenderComponent(args)],
            personHeldComponent: ecs.personHeldComponent(args),
            managedComponent: ecs.managedComponent(args),
            constructionComponent: ecs.memoryConstructionComponent(args),
            createExecutables: [ecs.makeExtraPathsExec(args)].concat([ecs.openMenu(args)]),
            focusPathStopComponent: ecs.constructionFocusPathStopComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            tickComponent: ecs.constructionTickComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    fantasyMine_personDConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fantasyMine_personDConstruction)));
        return ecs.construction(args);
    },
    towerTickModifierRenderComponent: function(args){
        if (args.key === undefined){ throw "ECS fail " + args.key + " missing key"; }
        if (args.image === undefined){ throw "ECS fail " + args.key + " missing image"; }
        return {
            render: towerTickModifierRender,
            image: args['image'],
            priority: 11,
            key: args['key'],
            getTowerForTickModifierRender: getTowerForTickModifierRender,
            renderState: "default"
        };
    },
    dating: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dating)));
        args["isaacEntities"] = generateOpinionIsaacs(args);
        return {
            isaacEntityComponent: ecs.isaacEntityComponent(args),
            opinionComponent: ecs.opinionComponent(args),
            focusTickResponseComponent: (args.aliveEntities) ? ecs.aliveMakeComponent(args) : undefined,
            personHeldComponent: ecs.personHeldComponent(args),
            createExecutables: [ecs.datingCreateExec(args)],
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            focusMakingComponent: (args.focusCount) ? ecs.focusMakingComponent(args) : undefined,
            tickComponent: ecs.opinionTickComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    memoryGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memoryGoal)));
        args["touchable"] = true;
        args["particleKey"] = PARAMETERS.newParticle;
        args["startEntityKey"] = args.sourceKey;
        args["canExpandInto"] = false;
        args["playerKillable"] = !!(args.constructed);
        args["killOnTransform"] = (args["killOnTransform"] === undefined) ? false : args["killOnTransform"];
        return {
            uiActionComponent: ecs.buildingUiActionComponent(args),
            onTraitComponent: ecs.onTraitComponent(args),
            goalTrackComponent: ecs.memoryGoalTrackComponent(args),
            createExecutables: [ecs.checkManagedComponentExec(args), (args.randomPosition) ? ecs.randomOpenPositionExec(args) : undefined, ecs.makeExtraPathsExec(args), ecs.chkForPlayerInterrupt(args), ecs.buildingConnectExec(args), (args.timeToDeath) ? ecs.timedDifficultyModifyExec(args) : undefined],
            timedDeathComponent: (args.timeToDeath) ? ecs.worldGoalTimedDeathComponent(args) : undefined,
            uiDataComponent: ecs.uiDataComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            rendererComponents: [ecs.worldGoalRenderComponent(args), ecs.progressRenderComponent(args), ecs.timedDeathRenderComponent(args)],
            focusTickResponseComponent: (args.aliveEntities) ? ecs.aliveMakeComponent(args) : undefined,
            managedComponent: (args.sourceTrait) ? ecs.managedComponent(args) : undefined,
            flipComponent: (args.flipEntity) ? ecs.flipComponent(args) : undefined,
            worldGoalComponent: ecs.worldGoalComponent(args),
            focusPathStopComponent: ecs.worldGoalFocusPathStopComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            positionComponent: ecs.positionComponent(args),
            notificationComponents: [ecs.worldGoalNotificationComponent(args)],
            junctionComponent: ecs.junctionComponent(args),
            pauseComponent: ecs.pauseComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    unpackGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.unpackGoal)));
        return ecs.worldGoal(args);
    },
    allCurrencyRenderClear: function(args){
        return {
            priority: 1,
            renderState: "default",
            render: allCurrencyRenderClear
        };
    },
    joyResourceModifier: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.joyResourceModifier)));
        return ecs.resourceModifier(args);
    },
    memoryResource: function(args){
        args["touchable"] = true;
        args["canExpandInto"] = false;
        return {
            interactionComponent: ecs.interactionComponent(args),
            junctionComponent: ecs.junctionComponent(args),
            rendererComponents: [ecs.pathStopRenderComponent(args)],
            focusTickResponseComponent: ecs.memoryResourceFocusTickResponseComponent(args),
            managedComponent: ecs.managedComponent(args),
            createExecutables: [ecs.makeExtraPathsExec(args)],
            focusPathStopComponent: ecs.resourceFocusPathStopComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            killExecutables: [ecs.killResourceCurrencies(args)],
            descriptionComponent: ecs.descriptionComponent(args),
            resourceComponent: ecs.resourceComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    gameEndRenderComponent: function(args){
        return {
            priority: 24,
            renderState: "default",
            render: gameEndRender
        };
    },
    dateGreatMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateGreatMine)));
        return ecs.emotionResource(args);
    },
    goalHelpInfoPanel: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.goalHelpInfoPanel)));
        return ecs.helpInfoPanel(args);
    },
    buyTimeMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.buyTimeMenu)));
        return ecs.goalCompleteMenu(args);
    },
    allGoalPanel: function(args){
        args["y"] = 5;
        args["x"] = (width - 800) / 2;
        args["key"] = PARAMETERS.allGoalPanel;
        args["size"] = [800, 300];
        return {
            rendererComponents: [ecs.allGoalPanelRenderComponent(args)],
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            uiClickComponent: ecs.panelUiClickComponent(args),
            panelComponent: ecs.panelComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    fearFlashMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fearFlashMine)));
        return ecs.emotionResource(args);
    },
    tutorialDefenseTab: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialDefenseTab)));
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tutorialTextComponent: ecs.tutorialDefenseTabTutorialTextComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            rendererComponents: [ecs.menuTutorialTextRenderComponent(args)]
        };
    },
    ringExciteMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.ringExciteMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    needsSupport: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.needsSupport)));
        return ecs.opinion(args);
    },
    controlPanel: function(args){
        return {
            panelTrackComponent: ecs.panelTrackComponent(args),
            keyPressComponent: ecs.controlPanelKeyPressComponent(args)
        };
    },
    dateGreatMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateGreatMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    traitParticleEffect: function(args){
        args["impulseTimeToCheck"] = (args["impulseTimeToCheck"] === undefined) ? 0.1 : args["impulseTimeToCheck"];
        args["startParticles"] = (args["startParticles"] === undefined) ? 10 : args["startParticles"];
        return {
            healthComponent: ecs.singleHealthComponent(args),
            lifeSentinelComponent: ecs.traitParticleEffectLifeSentinelComponent(args),
            rendererComponents: [ecs.particleEffectRenderComponent(args)]
        };
    },
    clickComponent: function(args){
        if (args.onClick === undefined){ throw "ECS fail " + args.key + " missing onClick"; }
        return {
            onClick: args['onClick']
        };
    },
    closestOpenPositionExec: function(args){
        if (args.startPos === undefined){ throw "ECS fail " + args.key + " missing startPos"; }
        return {
            execute: closestOpenPositionExec,
            startPos: args['startPos']
        };
    },
    fearTickModifier: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fearTickModifier)));
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tickModifyComponent: ecs.fearTickModifyComponent(args),
            rendererComponents: [ecs.towerTickModifierRenderComponent(args)],
            dayResponseComponent: ecs.killDayEndComponent(args),
            timedDeathComponent: ecs.timedDeathComponent(args)
        };
    },
    work: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.work)));
        return ecs.currency(args);
    },
    flirtAction_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flirtAction_personD)));
        return ecs.personCurrencyAction(args);
    },
    trainComponent: function(args){
        return {
            getCurrenciesHeld: getCurrenciesHeld,
            carriage: undefined,
            calculateTrainCurrencies: calculateTrainCurrencies,
            lastDrawPos: [0, 0],
            getCurrentCarriage: getCurrentCarriage
        };
    },
    confessFearMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confessFearMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    memory_dateGood: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_dateGood)));
        return ecs.memory(args);
    },
    goalTrackComponent: function(args){
        if (args.checkCompleted === undefined){ throw "ECS fail " + args.key + " missing checkCompleted"; }
        return {
            successExecutables: [ecs.goalCompleteExec(args)],
            checkCompleted: args['checkCompleted']
        };
    },
    personCreateExec: function(args){
        return {
            execute: personCreateExec
        };
    },
    swallowOpinionComponent: function(args){
        return {
            previousOpinions: undefined
        };
    },
    carriageComponent: function(args){
        return {
            direction: 0,
            holding: []
        };
    },
    shameMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.shameMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    controlComponent: function(args){
        if (args.makeEntity === undefined){ throw "ECS fail " + args.key + " missing makeEntity"; }
        if (args.canMakeEntity === undefined){ throw "ECS fail " + args.key + " missing canMakeEntity"; }
        if (args.checkMakePos === undefined){ throw "ECS fail " + args.key + " missing checkMakePos"; }
        return {
            aiVal: (args["aiVal"] === undefined) ? 0 : args["aiVal"],
            checkMakePos: args['checkMakePos'],
            isNew: true,
            canMakeEntity: args['canMakeEntity'],
            currenciesMade: (args["currenciesMade"] === undefined) ? [] : args["currenciesMade"],
            wrappedFunction: undefined,
            managerKey: (args["managerKey"] === undefined) ? args.sourceKey : args["managerKey"],
            makeEntity: args['makeEntity'],
            getAiVal: controlComponentGetAiVal,
            getWrappedFunction: getWrappedFunction
        };
    },
    lostControlDeathExec: function(args){
        return {
            execute: lostControlDeathExec
        };
    },
    tutorialPlaceEnergyComponent: function(args){
        return {
            entityToMark: args['entityToMark'],
            tutorialDone: tutorialTextTutorialDone,
            checkTutorialUiKey: checkTutorialPlaceEnergyKey,
            nextEntities: (args["nextEntities"] === undefined) ? undefined : args["nextEntities"]
        };
    },
    utilityPanel: function(args){
        return {
            panelComponent: ecs.setPanelComponent(args),
            absolutePositionComponent: ecs.utilityPanelAbsolutePositionComponent(args),
            rendererComponents: [ecs.utilityEntityRenderComponent(args)],
            uiClickComponent: ecs.panelUiClickComponent(args)
        };
    },
    keyPressComponent: function(args){
        if (args.handleKeyPress === undefined){ throw "ECS fail " + args.key + " missing handleKeyPress"; }
        return {
            handleKeyPress: args['handleKeyPress']
        };
    },
    descriptionComponent: function(args){
        return {
            description: (args["description"] === undefined) ? "" : args["description"]
        };
    },
    currencyRenderEntity: function(args){
        args["key"] = PARAMETERS.currencyRenderEntity;
        return {
            currencyRenderEntityComponent: ecs.currencyRenderEntityComponent(args),
            identityComponent: ecs.identityComponent(args),
            rendererComponents: [ecs.allCurrencyRenderClear(args), ecs.allCurrencyRenderComponent(args)]
        };
    },
    fear: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fear)));
        return ecs.creep(args);
    },
    killCurrency: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            createExecutables: [ecs.killCurrencyExec(args), ecs.killEntity(args)]
        };
    },
    cursor: function(args){
        return {
            rendererComponents: [ecs.cursorRenderComponent(args)]
        };
    },
    openMenuClickComponent: function(args){
        return {
            onClick: openMenuOnClick
        };
    },
    confesssion_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confesssion_personB)));
        return ecs.personCurrency(args);
    },
    confesssion_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confesssion_personD)));
        return ecs.personCurrency(args);
    },
    impulseFocusStop: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            focusPathStopComponent: ecs.impulseFocusPathStopComponent(args),
            positionComponent: ecs.positionComponent(args)
        };
    },
    flirtTower_personDConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flirtTower_personDConstruction)));
        return ecs.construction(args);
    },
    sadnessTickModifier: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.sadnessTickModifier)));
        return ecs.towerTickModifier(args);
    },
    applyTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.applyTowerConstruction)));
        return ecs.memoryConstruction(args);
    },
    crush: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.crush)));
        return ecs.opinion(args);
    },
    personBPathMotherland: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.personBPathMotherland)));
        return ecs.pathMaker(args);
    },
    memory_shame: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_shame)));
        return ecs.memory(args);
    },
    buildingConnectExec: function(args){
        return {
            execute: buildingConnectExec,
            startEntityKey: (args["startEntityKey"] === undefined) ? undefined : args["startEntityKey"]
        };
    },
    boost: function(args){
        return {
            identityComponent: ecs.identityComponent(args),
            rendererComponents: [ecs.boostRenderComponent(args)],
            boostSlotComponent: ecs.boostSlotComponent(args),
            createExecutables: [ecs.randomOpenPositionExec(args)],
            positionComponent: ecs.positionComponent(args)
        };
    },
    memory_dateSad: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_dateSad)));
        return ecs.memory(args);
    },
    boostSlotComponent: function(args){
        if (args.traits === undefined){ throw "ECS fail " + args.key + " missing traits"; }
        return {
            traits: args['traits'],
            checkEntityForBoost: checkEntityForBoost
        };
    },
    depositTickComponent: function(args){
        return {
            tick: depositTick
        };
    },
    tickEmotionComponent: function(args){
        if (args.currency === undefined){ throw "ECS fail " + args.key + " missing currency"; }
        return {
            currency: args['currency'],
            checkTickContinue: checkTickContinue,
            traits: (args["traits"] === undefined) ? [] : args["traits"]
        };
    },
    currencyRenderComponent: function(args){
        return {
            priority: 12,
            image: "currency",
            renderState: "default",
            render: currencyRender,
            renderPos: undefined
        };
    },
    defineSelf: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.defineSelf)));
        return ecs.emotionSource(args);
    },
    autoPathReplaceMarker: function(args){
        return {
            getReplaceKeys: autoPathGetReplaceKeys
        };
    },
    checkMakeDeposit: function(args){
        return {
            execute: checkMakeDeposit,
            checkMakeDeposit: (args["checkMakeDeposit"] === undefined) ? true : args["checkMakeDeposit"]
        };
    },
    socialButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.socialButton)));
        return ecs.personGoalButton(args);
    },
    timeForceExec: function(args){
        return {
            execute: timeForceExec
        };
    },
    autosaveNotificationComponent: function(args){
        return {
            notificationFunctions: simpleDict(PARAMETERS.goalComplete, autosaveOnGoalComplete),
            serializeComponent: ecs.notificationSerializeComponent(args)
        };
    },
    rentMaker: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.rentMaker)));
        return ecs.dailyEntityMaker(args);
    },
    cabChoreGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cabChoreGoal)));
        return ecs.worldGoal(args);
    },
    sadness: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.sadness)));
        return ecs.creep(args);
    },
    personD_ext: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.personD_ext)));
        return ecs.sourceExtension(args);
    },
    focusComponent: function(args){
        return {
            focusMax: 100,
            focusUsed: 0
        };
    },
    addPersonAndOpinion: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            createExecutables: [ecs.addPersonAndOpinionExec(args)]
        };
    },
    goalButton: function(args){
        args["subPanel"] = (args["subPanel"] === undefined) ? "Goals" : args["subPanel"];
        return {
            uiActionComponent: ecs.buttonUiActionComponent(args),
            buttonControlComponent: ecs.goalButtonControlComponent(args),
            createExecutables: [ecs.killIfFlagExec(args), ecs.markTraitNew(args)],
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            uiDataComponent: ecs.uiDataComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    fadingMemoryComponent: function(args){
        if (args.sourceKey === undefined){ throw "ECS fail " + args.key + " missing sourceKey"; }
        if (args.source2Key === undefined){ throw "ECS fail " + args.key + " missing source2Key"; }
        return {
            oldTrait: undefined,
            sourceKey: args['sourceKey'],
            source2Key: args['source2Key']
        };
    },
    creepCreateExec: function(args){
        if (args.sourceId === undefined){ throw "ECS fail " + args.key + " missing sourceId"; }
        return {
            firstTick: (args["firstTick"] === undefined) ? true : args["firstTick"],
            makeTrain: (args["makeTrain"] === undefined) ? true : args["makeTrain"],
            execute: creepCreateExec,
            creepTrainPath: PARAMETERS.singleUseTrainPath,
            sourceId: args['sourceId'],
            destination: (args["destination"] === undefined) ? PARAMETERS.self : args["destination"]
        };
    },
    talkButton_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.talkButton_personD)));
        return ecs.personTowerButton(args);
    },
    lifeThreatMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.lifeThreatMine)));
        return ecs.emotionResource(args);
    },
    industryMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.industryMine)));
        return ecs.resource(args);
    },
    learnSpyNewsGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.learnSpyNewsGoal)));
        return ecs.worldGoal(args);
    },
    memoryTowerButtonIssacEntityComponent: function(args){
        return {
            endLifecycle: trueFunction,
            startLifecycle: makeIsaacLifecycleEntities,
            makeLifecycleEntities: makeLifecycleEntities,
            entitiesMade: [],
            isaacEntities: (args["isaacEntities"] === undefined) ? [] : args["isaacEntities"]
        };
    },
    timedMakeEntities: function(args){
        return {
            execute: timedMakeEntities,
            timedOutEntities: (args["timedOutEntities"] === undefined) ? [] : args["timedOutEntities"]
        };
    },
    junctionComponent: function(args){
        return {
            heldItems: [],
            junctionItemForKey: junctionItemForKey
        };
    },
    ringExciteMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.ringExciteMine)));
        return ecs.emotionResource(args);
    },
    selfExtensionComponent: function(args){
        return {
            emotion: undefined,
            setEmotion: selfExtensionSetEmotion,
            emotionLife: 0
        };
    },
    findCabJobGoalConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.findCabJobGoalConstruction)));
        return ecs.construction(args);
    },
    buttonManageComponent: function(args){
        return {
            renderButton: renderButton,
            panelButtons: [],
            lineHeight: (args["lineHeight"] === undefined) ? 16 : args["lineHeight"]
        };
    },
    opinion: function(args){
        args["isaacEntities"] = generateOpinionIsaacs(args);
        return {
            isaacEntityComponent: ecs.isaacEntityComponent(args),
            opinionComponent: ecs.opinionComponent(args),
            focusTickResponseComponent: (args.aliveEntities) ? ecs.aliveMakeComponent(args) : undefined,
            personHeldComponent: ecs.personHeldComponent(args),
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            focusMakingComponent: (args.focusCount) ? ecs.focusMakingComponent(args) : undefined,
            tickComponent: ecs.opinionTickComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    confessionGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confessionGoal)));
        return ecs.worldGoal(args);
    },
    playerMadeComponent: function(args){
        return {
            playerKillable: (args["playerKillable"] === undefined) ? true : args["playerKillable"],
            canExpandInto: (args["canExpandInto"] === undefined) ? true : args["canExpandInto"]
        };
    },
    sadnessMap: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.sadnessMap)));
        return ecs.emotionResourceMap(args);
    },
    chapter2Menu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chapter2Menu)));
        return ecs.gameStageStartMenu(args);
    },
    daydreamMine_personBConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.daydreamMine_personBConstruction)));
        return ecs.construction(args);
    },
    jobQuotaMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.jobQuotaMenu)));
        return ecs.goalCompleteMenu(args);
    },
    currencyOverload: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            currencyOverloadComponent: ecs.currencyOverloadComponent(args)
        };
    },
    memoryStopMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memoryStopMenu)));
        return ecs.goalCompleteMenu(args);
    },
    damageComponent: function(args){
        args["charges"] = (args["charges"] === undefined) ? 5 : args["charges"];
        if (args.damageMultipliers === undefined){ throw "ECS fail " + args.key + " missing damageMultipliers"; }
        if (args.charges === undefined){ throw "ECS fail " + args.key + " missing charges"; }
        return {
            checkTargetEntity: checkTargetEntity,
            ticks: 0,
            charges: args['charges'],
            targetCount: (args["targetCount"] === undefined) ? 1 : args["targetCount"],
            damage: (args["damage"] === undefined) ? 10 : args["damage"],
            damageMultipliers: args['damageMultipliers'],
            hitPos: undefined,
            maxCharges: args['charges'],
            calculateDamage: calculateDamage,
            getHitPos: getHitPos,
            impulseTicks: (args["impulseTicks"] === undefined) ? 0 : args["impulseTicks"]
        };
    },
    prideJoy: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.prideJoy)));
        return ecs.emotionSource(args);
    },
    cleanAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cleanAction)));
        return ecs.currencyAction(args);
    },
    timedDifficultyModifyExec: function(args){
        return {
            execute: timedDifficultyModify
        };
    },
    talkTower_personDConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.talkTower_personDConstruction)));
        return ecs.construction(args);
    },
    constructionTickComponent: function(args){
        return {
            tick: constructionTick
        };
    },
    notificationCurrency: function(args){
        return {
            notificationCurrencyComponent: ecs.notificationCurrencyComponent(args),
            notificationComponents: [ecs.currencyNotificationComponent(args)]
        };
    },
    lifeSentinelComponent: function(args){
        if (args.checkAlive === undefined){ throw "ECS fail " + args.key + " missing checkAlive"; }
        return {
            checkAlive: args['checkAlive']
        };
    },
    memoryGoalCompleteExec: function(args){
        return {
            execute: memoryGoalCompleteExec,
            bonusTime: (args["bonusTime"] === undefined) ? 30 : args["bonusTime"],
            flareMemory: (args["flareMemory"] === undefined) ? false : args["flareMemory"],
            killMemory: (args["killMemory"] === undefined) ? false : args["killMemory"]
        };
    },
    tutorialDefense: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialDefense)));
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tutorialTextComponent: ecs.tutorialTextAdulthoodComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            rendererComponents: [ecs.tutorialTextRenderComponent(args)]
        };
    },
    damageIndicatorRenderComponent: function(args){
        if (args.sourceId === undefined){ throw "ECS fail " + args.key + " missing sourceId"; }
        return {
            priority: 12,
            sourceId: args['sourceId'],
            renderState: "default",
            render: damageIndicatorRender
        };
    },
    flashback_joyConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flashback_joyConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    dateLuckyMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateLuckyMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    dayPanel: function(args){
        return {
            panelComponent: ecs.setPanelComponent(args),
            absolutePositionComponent: ecs.dayPanelAbsolutePositionComponent(args),
            rendererComponents: [ecs.dayPanelRenderComponent(args)],
            uiClickComponent: ecs.panelUiClickComponent(args)
        };
    },
    executable: function(args){
        if (args.execute === undefined){ throw "ECS fail " + args.key + " missing execute"; }
        return {
            execute: args['execute']
        };
    },
    goalCompleteExec: function(args){
        return {
            execute: goalCompleteExec
        };
    },
    panelComponent: function(args){
        return {
            panelButtons: [],
            expandPanel: expandPanel,
            minimized: true,
            canClose: true,
            closed: true,
            ignoreWhenMinimized: true
        };
    },
    helpInfoPanel: function(args){
        args["size"] = [width, 678];
        return {
            rendererComponents: [ecs.helpInfoPanelRenderComponent(args)],
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            uiClickComponent: ecs.panelUiClickComponent(args),
            panelComponent: ecs.panelComponent(args),
            pauseComponent: ecs.pauseComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    trainPathWarningRender: function(args){
        return {
            priority: 11,
            renderState: "default",
            render: trainPathWarningRender
        };
    },
    flareMemoryButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flareMemoryButton)));
        return ecs.memoryGoalButton(args);
    },
    cashDeposit: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cashDeposit)));
        return ecs.deposit(args);
    },
    memoryTickComponent: function(args){
        return {
            lastManagedCount: 0,
            tick: memoryTick
        };
    },
    memory: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory)));
        args["touchable"] = true;
        args["path"] = [];
        args["interactionKeys"] = [PARAMETERS.self];
        args["randomTraitPosition"] = false;
        args["health"] = (args["health"] === undefined) ? 225 : args["health"];
        args["panelKey"] = (args["panelKey"] === undefined) ? PARAMETERS.traitPanel : args["panelKey"];
        return {
            uiActionComponent: ecs.buildingUiActionComponent(args),
            memoryComponent: ecs.memoryComponent(args),
            experienceComponent: ecs.experienceComponent(args),
            createExecutables: [ecs.memoryFindPositionExec(args), ecs.builtSourceCreateExec(args)],
            timedDeathComponent: (PARAMETERS.timedMemories) ? ecs.timedDeathComponent(args) : undefined,
            uiDataComponent: ecs.uiDataComponent(args),
            entityManagerComponent: ecs.entityManagerComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            isaacEntityComponent: ecs.memoryIsaacEntityComponent(args),
            rendererComponents: [ecs.builtSourceRendererComponent(args)],
            focusTickResponseComponent: (args.aliveEntities) ? ecs.focusTickAliveMakerComponent(args) : undefined,
            tickComponent: ecs.memoryTickComponent(args),
            menuCloseComponent: ecs.traitMenuCloseComponent(args),
            personHeldComponent: (args.personHeld) ? ecs.personHeldComponent(args) : undefined,
            resourceComponent: undefined,
            clickComponent: ecs.fullMenuClickComponent(args),
            killExecutables: [ecs.killExtensions(args)],
            healthComponent: ecs.healthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            positionComponent: ecs.positionComponent(args),
            mapSpawnComponent: ecs.builtSourceMapSpawnComponent(args),
            junctionComponent: ecs.junctionComponent(args),
            playerMadeComponent: ecs.fixedPlayerMadeComponent(args)
        };
    },
    understandAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.understandAction)));
        return ecs.currencyAction(args);
    },
    carriage: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            positionComponent: ecs.positionComponent(args),
            carriageComponent: ecs.carriageComponent(args)
        };
    },
    cleanChore: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cleanChore)));
        return ecs.dailyEntityMaker(args);
    },
    sourceExtensionRenderComponent: function(args){
        return {
            outlineImage: "activeOutline",
            render: sourceExtensionRender,
            wobble: 0,
            image: args['image'],
            priority: 5,
            timeToBlink: 1,
            newImage: "newOutline",
            getWobble: getWobble,
            renderState: "default",
            disabledImage: "trait-aaaaaa"
        };
    },
    tutorialTextAdulthoodComponent: function(args){
        return {
            entityToMark: args['entityToMark'],
            tutorialDone: tutorialTextTutorialDone,
            checkTutorialUiKey: checkTutorialAdulthoodUiKey,
            nextEntities: (args["nextEntities"] === undefined) ? undefined : args["nextEntities"]
        };
    },
    groupPanel: function(args){
        args["y"] = 5;
        args["x"] = (width - 800) / 2;
        args["size"] = [800, 300];
        return {
            panelComponent: ecs.panelComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            uiClickComponent: ecs.panelUiClickComponent(args),
            identityComponent: ecs.identityComponent(args),
            uniqueComponent: ecs.uniqueComponent(args)
        };
    },
    buttonUiActionComponent: function(args){
        return {
            getUiActions: getUiActions,
            inspectFunction: undefined,
            generateUiActions: buttonGenerateUiActions,
            memoizedActions: undefined
        };
    },
    understandMemoryGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.understandMemoryGoal)));
        return ecs.memoryGoal(args);
    },
    killEntityComponent: function(args){
        return {
            entitiesToKill: (args["entitiesToKill"] === undefined) ? [] : args["entitiesToKill"],
            execute: executeKillEntities
        };
    },
    fightProgramButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fightProgramButton)));
        return ecs.goalButton(args);
    },
    markTraitNew: function(args){
        return {
            execute: markTraitNewExec,
            particleEffect: "newTraitParticle"
        };
    },
    trainPathMaker: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            rendererComponents: [ecs.trainPathMakerHighlightRenderComponent(args)],
            killExecutables: [ecs.trainPathMakerKill(args)],
            trainPathMakerComponent: ecs.trainPathMakerComponent(args),
            tickComponent: ecs.trainPathMakerTickComponent(args)
        };
    },
    personTowerButton: function(args){
        args["subPanel"] = (args["subPanel"] === undefined) ? "Towers" : args["subPanel"];
        args["multiTrait"] = (args["multiTrait"] === undefined) ? false : args["multiTrait"];
        return {
            healthComponent: ecs.singleHealthComponent(args),
            uiActionComponent: ecs.buttonUiActionComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            buttonControlComponent: ecs.towerControlComponent(args),
            personHeldComponent: ecs.personHeldComponent(args),
            createExecutables: [ecs.markTraitNew(args)],
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            killExecutables: [ecs.killTowerButtonExec(args)],
            descriptionComponent: ecs.descriptionComponent(args),
            uniqueComponent: (args.timeToDeath === undefined && !args.multiTrait) ? ecs.uniqueComponent(args) : undefined,
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    intelligentPathCity: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.intelligentPathCity)));
        return ecs.pathMaker(args);
    },
    disavowalTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.disavowalTowerConstruction)));
        return ecs.construction(args);
    },
    train: function(args){
        return {
            rendererComponents: [ecs.trainRenderComponent(args)],
            trainComponent: ecs.trainComponent(args),
            trainVelocityComponent: ecs.trainVelocityComponent(args),
            createExecutables: [ecs.trainCreateExec(args)],
            killExecutables: [ecs.trainKillExec(args)],
            healthComponent: ecs.singleHealthComponent(args),
            positionComponent: ecs.positionComponent(args)
        };
    },
    adult: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.adult)));
        args["touchable"] = true;
        args["tendPosition"] = [0, 0];
        args["health"] = (args["health"] === undefined) ? 2000 : args["health"];
        args["panelKey"] = (args["panelKey"] === undefined) ? PARAMETERS.traitPanel : args["panelKey"];
        return {
            killExecutables: [ecs.killExtensions(args)],
            isaacEntityComponent: ecs.isaacEntityComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            rendererComponents: [ecs.builtSourceRendererComponent(args)],
            uiActionComponent: ecs.buildingUiActionComponent(args),
            focusTickResponseComponent: (args.aliveEntities) ? ecs.focusTickAliveMakerComponent(args) : undefined,
            mapSpawnComponent: ecs.builtSourceMapSpawnComponent(args),
            experienceComponent: ecs.experienceComponent(args),
            personHeldComponent: (args.personHeld) ? ecs.personHeldComponent(args) : undefined,
            createExecutables: [ecs.builtSourceCreateExec(args), ecs.makeExtraPathsExec(args)],
            junctionComponent: ecs.junctionComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            entityManagerComponent: ecs.entityManagerComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.healthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            menuCloseComponent: ecs.traitMenuCloseComponent(args),
            playerMadeComponent: ecs.fixedPlayerMadeComponent(args)
        };
    },
    impulse: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.impulse)));
        return ecs.currency(args);
    },
    fearFlash: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fearFlash)));
        return ecs.emotionSource(args);
    },
    towerHelpInfoPanel: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.towerHelpInfoPanel)));
        return ecs.helpInfoPanel(args);
    },
    addOpinionExec: function(args){
        return {
            execute: addOpinionExec,
            opinionToAdd: (args["opinionToAdd"] === undefined) ? undefined : args["opinionToAdd"]
        };
    },
    constructionClickComponent: function(args){
        return {
            onClick: constructOnClick
        };
    },
    spendTimeMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.spendTimeMenu)));
        return ecs.goalCompleteMenu(args);
    },
    chapterPanelRenderComponent: function(args){
        return {
            insetColor: "#000000",
            canShowGoal: chapterPanelCanShowGoal,
            tickImage: "tick",
            fullAnimationTime: 0.1,
            yTextOffset: 5,
            backImage: "uiBack",
            drawImageButton: drawImageButton,
            animatingTime: 0.1,
            priority: 18,
            render: outPanelRender,
            drawPanelButton: drawPanelButton,
            snapPanelPos: snapPanelPos,
            showGoal: chapterPanelShowGoal,
            renderTime: 0,
            textColor: "#000000",
            renderState: "default",
            goalOpenFunctions: {},
            specificRender: chapterPanelRender,
            drawHelpButton: drawHelpButton
        };
    },
    damageUpgradeComponent: function(args){
        if (args.towerKeys === undefined){ throw "ECS fail " + args.key + " missing towerKeys"; }
        if (args.damageUpgrade === undefined){ throw "ECS fail " + args.key + " missing damageUpgrade"; }
        return {
            damageUpgrade: args['damageUpgrade'],
            towerKeys: args['towerKeys']
        };
    },
    confessTower_personBConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confessTower_personBConstruction)));
        return ecs.construction(args);
    },
    confessFearMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confessFearMine)));
        return ecs.emotionResource(args);
    },
    trainPathMakingPanelRender: function(args){
        return {
            insetColor: "#000000",
            completeFunction: undefined,
            fullAnimationTime: 0.1,
            yTextOffset: 5,
            backImage: "uiBack",
            killStopFunctions: [],
            drawImageButton: drawImageButton,
            animatingTime: 0.1,
            trainIcon: "trainIcon",
            priority: 18,
            render: trainPathMakingPanelRender,
            drawPanelButton: drawPanelButton,
            snapPanelPos: snapPanelPos,
            renderTime: 0,
            textColor: "#000000",
            renderState: "default",
            blinkTime: 0.9,
            specificRender: args['specificRender'],
            drawHelpButton: drawHelpButton
        };
    },
    walkDateMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.walkDateMenu)));
        return ecs.goalCompleteMenu(args);
    },
    uiClickComponent: function(args){
        return {
            uiClick: (args["uiClick"] === undefined) ? args['trueFunction'] : args["uiClick"]
        };
    },
    driveTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.driveTower)));
        return ecs.tower(args);
    },
    pointNotification: function(args){
        args["notificationKey"] = PARAMETERS.addPoints;
        args["notificationArgs"] = {points: args.points};
        return {
            healthComponent: ecs.singleHealthComponent(args),
            notifyComponent: ecs.notifyComponent(args)
        };
    },
    carryTrain: function(args){
        return {
            rendererComponents: [ecs.trainRenderComponent(args)],
            trainComponent: ecs.trainComponent(args),
            trainVelocityComponent: ecs.trainVelocityComponent(args),
            createExecutables: [ecs.trainCreateExec(args)],
            killExecutables: [ecs.trainKillExec(args)],
            tickComponent: ecs.carryTrainTickComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            positionComponent: ecs.positionComponent(args)
        };
    },
    currencyRenderEntityComponent: function(args){
        return {
            addCurrencyToRender: addCurrencyToRender,
            currenciesToRender: []
        };
    },
    trainPath: function(args){
        return {
            rendererComponents: [ecs.trainPathTowerActiveRender(args), ecs.trainPathSecondRender(args), ecs.trainPathCircRenderComponent(args)],
            trainPathComponent: ecs.trainPathComponent(args),
            menuCloseComponent: ecs.trainPathMenuCloseComponent(args),
            createExecutables: [ecs.setTrainColor(args)],
            killExecutables: [ecs.blinkTrainStops(args)],
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            focusCostComponent: ecs.focusCostComponent(args)
        };
    },
    subconsciousRequestComponent: function(args){
        return {
            needsWorker: trueFunction,
            calculateWorkerTicks: calculateWorkerTicks,
            workerRequested: args.sourceTrait ? (args.sourceTrait + '_subconscious') : PARAMETERS.subconsciousWorkerKey,
            workersActive: [],
            workerCount: 1,
            subconsciousRequest: true,
            workerLife: 20
        };
    },
    nextDayEntity: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            dayResponseComponent: ecs.nextDayEntityDayResponseComponent(args)
        };
    },
    killTrainExec: function(args){
        if (args.source === undefined){ throw "ECS fail " + args.key + " missing source"; }
        return {
            source: args['source'],
            execute: killTrainExec
        };
    },
    spyMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.spyMenu)));
        return ecs.goalCompleteMenu(args);
    },
    dateSad: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateSad)));
        return ecs.emotionSource(args);
    },
    boostRenderComponent: function(args){
        return {
            priority: 6,
            renderState: args['planeOfExistence'],
            render: boostRender
        };
    },
    fearFlashMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fearFlashMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    pathVelocityComponent: function(args){
        return {
            velocity: 1,
            isMoving: false,
            ticks: 0,
            isHeld: false,
            chosenPath: false
        };
    },
    spy: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.spy)));
        return ecs.currency(args);
    },
    tutorialWrongButtonComponent: function(args){
        return {
            tutorialDone: falseFunction
        };
    },
    chapter3Menu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chapter3Menu)));
        return ecs.gameStageStartMenu(args);
    },
    personB_ext: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.personB_ext)));
        return ecs.sourceExtension(args);
    },
    currencyBoost: function(args){
        return {
            rendererComponents: [ecs.boostRenderComponent(args)],
            boostSlotComponent: ecs.boostSlotComponent(args),
            createExecutables: [ecs.randomOpenPositionExec(args)],
            currencyBoostComponent: ecs.currencyBoostComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args)
        };
    },
    shameMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.shameMine)));
        return ecs.emotionResource(args);
    },
    activateRenderComponent: function(args){
        return {
            priority: 2,
            renderState: "default",
            activated: false,
            render: activateRender
        };
    },
    hundredHealthComponent: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.hundredHealthComponent)));
        return {
            kill: killHealthComponent,
            damageFactor: (args["damageFactor"] === undefined) ? 1 : args["damageFactor"],
            health: args['health'],
            maxHealth: args['health']
        };
    },
    agent: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.agent)));
        return ecs.entityReplace(args);
    },
    threatMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.threatMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    currency: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.currency)));
        args["tags"] = (args.tags) ? args.tags.concat([args.key]) : [args.key];
        return {
            pathVelocityComponent: ecs.pathVelocityComponent(args),
            rendererComponents: [ecs.currencyRenderComponent(args)],
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            dayResponseComponent: (args.checkDayEnd) ? ecs.currencyDayResponseComponent(args) : undefined
        };
    },
    caution: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.caution)));
        return ecs.currency(args);
    },
    autoPathMaker: function(args){
        return {
            replaceMarker: ecs.autoPathReplaceMarker(args)
        };
    },
    contact_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.contact_personD)));
        return ecs.personCurrency(args);
    },
    focusPanel: function(args){
        return {
            panelComponent: ecs.setPanelComponent(args),
            absolutePositionComponent: ecs.focusPanelAbsolutePositionComponent(args),
            rendererComponents: [ecs.focusPanelRenderComponent(args)],
            uiClickComponent: ecs.panelUiClickComponent(args)
        };
    },
    interactionComponent: function(args){
        return {
            connectionKeys: [],
            interactionKeys: (args["interactionKeys"] === undefined) ? PARAMETERS.interactionKeys : args["interactionKeys"],
            canInteractSelf: canInteractSelf,
            canInteract: canInteract
        };
    },
    endGoalExec: function(args){
        if (args.flag === undefined){ throw "ECS fail " + args.key + " missing flag"; }
        if (args.buttonKey === undefined){ throw "ECS fail " + args.key + " missing buttonKey"; }
        return {
            buttonKey: args['buttonKey'],
            execute: endGoalExecute,
            flag: args['flag']
        };
    },
    walkButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.walkButton)));
        return ecs.towerButton(args);
    },
    contactAction_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.contactAction_personD)));
        return ecs.personCurrencyAction(args);
    },
    daydreamMine_personDConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.daydreamMine_personDConstruction)));
        return ecs.construction(args);
    },
    qualityImprovement: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            personHeldComponent: (args.qualityEntity === undefined) ? ecs.personHeldComponent(args) : undefined,
            createExecutables: [ecs.improveQualityExec(args), ecs.killEntity(args)]
        };
    },
    allTrainPanel: function(args){
        args["y"] = 5;
        args["x"] = (width - 800) / 2;
        args["key"] = PARAMETERS.allTrainPanel;
        args["size"] = [500, 625];
        return {
            rendererComponents: [ecs.allTrainPanelRenderComponent(args)],
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            uiClickComponent: ecs.panelUiClickComponent(args),
            panelComponent: ecs.panelComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    sinDeposit: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.sinDeposit)));
        args["touchable"] = true;
        args["canExpandInto"] = false;
        return {
            junctionComponent: ecs.junctionComponent(args),
            rendererComponents: [ecs.pathStopRenderComponent(args), ecs.timedDeathRenderComponent(args)],
            uiActionComponent: ecs.buildingUiActionComponent(args),
            focusTickResponseComponent: ecs.aliveMakeComponent(args),
            depositComponent: ecs.depositComponent(args),
            managedComponent: (args.sourceTrait) ? ecs.managedComponent(args) : undefined,
            createExecutables: [ecs.checkMakeDeposit(args), ecs.depositPositionExec(args), ecs.makeExtraPathsExec(args), ecs.buildingConnectExec(args), (args.timeToDeath) ? ecs.timedDifficultyModifyExec(args) : undefined],
            focusPathStopComponent: ecs.depositFocusPathStopComponent(args),
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            clickComponent: ecs.fullMenuClickComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            tickComponent: ecs.depositTickComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.fixedPlayerMadeComponent(args)
        };
    },
    travel: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.travel)));
        return ecs.currency(args);
    },
    cabCompete: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cabCompete)));
        return ecs.workCompetition(args);
    },
    flirt_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flirt_personD)));
        return ecs.personCurrency(args);
    },
    constructionComponent: function(args){
        if (args.currencyRequested === undefined){ throw "ECS fail " + args.key + " missing currencyRequested"; }
        if (args.constructionIdentifier === undefined){ throw "ECS fail " + args.key + " missing constructionIdentifier"; }
        return {
            constructionIdentifier: args['constructionIdentifier'],
            killWithoutPath: (args["killWithoutPath"] === undefined) ? false : args["killWithoutPath"],
            ticked: (args["ticked"] === undefined) ? false : args["ticked"],
            construct: construct,
            currencyRequested: args['currencyRequested'],
            origCurrencyRequested: _.clone(args.currencyRequested)
        };
    },
    opinionConditionalExec: function(args){
        if (args.person === undefined){ throw "ECS fail " + args.key + " missing person"; }
        if (args.opinions === undefined){ throw "ECS fail " + args.key + " missing opinions"; }
        if (args.entitiesToMake === undefined){ throw "ECS fail " + args.key + " missing entitiesToMake"; }
        return {
            sourceId: (args["sourceId"] === undefined) ? undefined : args["sourceId"],
            person: args['person'],
            execute: opinionConditional,
            entitiesToMake: args['entitiesToMake'],
            opinions: args['opinions']
        };
    },
    makeAgentE: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.makeAgentE)));
        return ecs.addPersonAndOpinion(args);
    },
    selfDamageEffectRenderComponent: function(args){
        return {
            priority: 19,
            timeDrawn: 0,
            renderState: "default",
            animationTime: 0.3,
            render: selfDamageEffectRender
        };
    },
    flirtTower_personBConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flirtTower_personBConstruction)));
        return ecs.construction(args);
    },
    allGoalPanelRenderComponent: function(args){
        return {
            insetColor: "#000000",
            fullAnimationTime: 0.1,
            yTextOffset: 5,
            backImage: "uiBack",
            drawImageButton: drawImageButton,
            animatingTime: 0.1,
            priority: 18,
            render: outPanelRender,
            drawPanelButton: drawPanelButton,
            snapPanelPos: snapPanelPos,
            renderTime: 0,
            textColor: "#000000",
            renderState: "default",
            specificRender: allGoalPanelRender,
            drawHelpButton: drawHelpButton
        };
    },
    intelligent: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.intelligent)));
        return ecs.trait(args);
    },
    thinkAboutAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.thinkAboutAction)));
        return ecs.currencyAction(args);
    },
    experience: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.experience)));
        return ecs.currency(args);
    },
    tutorialUnpauseBuild: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialUnpauseBuild)));
        return ecs.tutorialUnpause(args);
    },
    autosaveTickComponent: function(args){
        return {
            lastChapterKey: "chapter1",
            tick: autosaveTick,
            ticksToAutosave: 1
        };
    },
    memory_disavowSadness: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_disavowSadness)));
        return ecs.memory(args);
    },
    menuModifier: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            menuModifierComponent: ecs.menuModifierComponent(args)
        };
    },
    trainKillExec: function(args){
        return {
            execute: trainKillExec
        };
    },
    demolishPathButton: function(args){
        return {
            rendererComponents: [ecs.demolishPathRenderComponent(args)]
        };
    },
    fightProgramMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fightProgramMenu)));
        return ecs.goalCompleteMenu(args);
    },
    towerFocusTickResponseComponent: function(args){
        return {
            isReady: false,
            isWaiting: false,
            focusTick: towerFocusTick
        };
    },
    dateLuckyMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateLuckyMine)));
        return ecs.emotionResource(args);
    },
    makeOnCreateEntityExec: function(args){
        args["makeOnCreateEntities"] = (args["makeOnCreateEntities"] === undefined) ? [] : args["makeOnCreateEntities"];
        return {
            execute: executeMakeEntities,
            entitiesToMake: args['makeOnCreateEntities'],
            adjacent: (args["adjacent"] === undefined) ? false : args["adjacent"]
        };
    },
    swallowOpinionKill: function(args){
        return {
            execute: swallowOpinionKill
        };
    },
    learnSpyNewsMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.learnSpyNewsMenu)));
        return ecs.goalCompleteMenu(args);
    },
    fearMap: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fearMap)));
        return ecs.emotionResourceMap(args);
    },
    bombPrep1Menu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.bombPrep1Menu)));
        return ecs.goalCompleteMenu(args);
    },
    learnSad: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.learnSad)));
        return ecs.emotionSource(args);
    },
    breakdownMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.breakdownMenu)));
        return ecs.goalCompleteMenu(args);
    },
    checkQualityTickComponent: function(args){
        if (args.qualityKey === undefined){ throw "ECS fail " + args.key + " missing qualityKey"; }
        if (args.minQuality === undefined){ throw "ECS fail " + args.key + " missing minQuality"; }
        if (args.minQualityEntities === undefined){ throw "ECS fail " + args.key + " missing minQualityEntities"; }
        return {
            qualityKey: args['qualityKey'],
            tick: checkQualityTick,
            minQualityEntities: args['minQualityEntities'],
            minQuality: args['minQuality']
        };
    },
    emotionSourceCreateExec: function(args){
        if (args.sourceKey === undefined){ throw "ECS fail " + args.key + " missing sourceKey"; }
        if (args.resourceKey === undefined){ throw "ECS fail " + args.key + " missing resourceKey"; }
        if (args.memoryKey === undefined){ throw "ECS fail " + args.key + " missing memoryKey"; }
        return {
            resourceKey: args['resourceKey'],
            sourceKey: args['sourceKey'],
            execute: emotionSourceCreateExec,
            memoryKey: args['memoryKey']
        };
    },
    memoryResourceButton: function(args){
        args["managerKey"] = args.sourceKey;
        args["multiTrait"] = true;
        args["subPanel"] = (args["subPanel"] === undefined) ? "Towers" : args["subPanel"];
        args["multiTrait"] = (args["multiTrait"] === undefined) ? false : args["multiTrait"];
        if (args.sourceKey === undefined){ throw "ECS fail " + args.key + " missing sourceKey"; }
        return {
            interactionComponent: ecs.interactionComponent(args),
            uiActionComponent: ecs.buttonUiActionComponent(args),
            buttonControlComponent: ecs.towerControlComponent(args),
            createExecutables: [ecs.markTraitNew(args)],
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            uiDataComponent: ecs.uiDataComponent(args),
            uniqueComponent: (args.timeToDeath === undefined && !args.multiTrait) ? ecs.uniqueComponent(args) : undefined,
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            killExecutables: [ecs.killTowerButtonExec(args)],
            identityComponent: ecs.identityComponent(args)
        };
    },
    memoryFadeExec: function(args){
        if (args.sourceKey === undefined){ throw "ECS fail " + args.key + " missing sourceKey"; }
        if (args.newTrait === undefined){ throw "ECS fail " + args.key + " missing newTrait"; }
        return {
            execute: memoryFadeExec,
            sourceKey: args['sourceKey'],
            newTrait: args['newTrait']
        };
    },
    killDayEndComponent: function(args){
        args["dayEnd"] = dayEndKillEntity;
        return {
            dayEnd: (args["dayEnd"] === undefined) ? undefined : args["dayEnd"],
            dayStart: (args["dayStart"] === undefined) ? undefined : args["dayStart"],
            dayChange: (args["dayChange"] === undefined) ? undefined : args["dayChange"]
        };
    },
    makeAllReplaceMarker: function(args){
        if (args.csv === undefined){ throw "ECS fail " + args.key + " missing csv"; }
        return {
            csv: args['csv'],
            getReplaceKeys: makeAllGetReplaceKeys
        };
    },
    flirt_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flirt_personB)));
        return ecs.personCurrency(args);
    },
    trainCreateExec: function(args){
        if (args.trainPath === undefined){ throw "ECS fail " + args.key + " missing trainPath"; }
        if (args.trainStartId === undefined){ throw "ECS fail " + args.key + " missing trainStartId"; }
        return {
            trainStartId: args['trainStartId'],
            execute: trainCreateExec,
            carriageKey: PARAMETERS.carriage,
            trainPath: args['trainPath']
        };
    },
    toastNotificationComponent: function(args){
        return {
            notificationFunctions: simpleDict(PARAMETERS.infoPanel, toastNotification)
        };
    },
    tutorialBuildPaint: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialBuildPaint)));
        return ecs.menuTutorialText(args);
    },
    fantasyMineButton_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fantasyMineButton_personB)));
        return ecs.personResourceButton(args);
    },
    cabChoreStartCheck: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cabChoreStartCheck)));
        return ecs.notifyEntityMaker(args);
    },
    ringExcite: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.ringExcite)));
        return ecs.emotionSource(args);
    },
    conditionalMake: function(args){
        return {
            replaceMarker: ecs.conditionalMakeReplaceMarker(args)
        };
    },
    clearTraitExec: function(args){
        if (args.trait === undefined){ throw "ECS fail " + args.key + " missing trait"; }
        return {
            trait: args['trait'],
            execute: clearTraitExec
        };
    },
    changeOpinion: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            personHeldComponent: ecs.personHeldComponent(args),
            createExecutables: [ecs.changeOpinionExec(args), ecs.killEntity(args)]
        };
    },
    rentGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.rentGoal)));
        return ecs.worldGoal(args);
    },
    spellComponent: function(args){
        if (args.spellCost === undefined){ throw "ECS fail " + args.key + " missing spellCost"; }
        if (args.spellFn === undefined){ throw "ECS fail " + args.key + " missing spellFn"; }
        return {
            spellCost: args['spellCost'],
            spellButtonFunction: undefined,
            spellFn: args['spellFn']
        };
    },
    prepareTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.prepareTowerConstruction)));
        return ecs.memoryConstruction(args);
    },
    constructionMenuCloseComponent: function(args){
        return {
            execute: constructionMenuCloseExec
        };
    },
    damageUpgrade: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            damageUpgradeComponent: ecs.damageUpgradeComponent(args)
        };
    },
    contactButton_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.contactButton_personB)));
        return ecs.personTowerButton(args);
    },
    creepRenderComponent: function(args){
        if (args.image === undefined){ throw "ECS fail " + args.key + " missing image"; }
        return {
            priority: 12,
            image: args['image'],
            renderState: "default",
            render: currencyRender,
            renderPos: undefined
        };
    },
    prideJoyMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.prideJoyMine)));
        return ecs.emotionResource(args);
    },
    dayPanelAbsolutePositionComponent: function(args){
        return {
            y: 678,
            x: 0,
            size: [300, 90]
        };
    },
    personKillExec: function(args){
        return {
            execute: personKillExec
        };
    },
    focusTickAliveMakerComponent: function(args){
        if (args.aliveEntities === undefined){ throw "ECS fail " + args.key + " missing aliveEntities"; }
        return {
            focusTick: aliveMakerFocusTick,
            aliveEntities: args['aliveEntities']
        };
    },
    buildPanel: function(args){
        return {
            panelComponent: ecs.setPanelComponent(args),
            absolutePositionComponent: ecs.buildPanelAbsolutePositionComponent(args),
            rendererComponents: [ecs.buildPanelRenderComponent(args)],
            uiClickComponent: ecs.panelUiClickComponent(args)
        };
    },
    helpPanelComponent: function(args){
        return {
            openHelpPanel: openHelpPanel
        };
    },
    impulseFocusPathStopComponent: function(args){
        return {
            interactWithTrain: impulseFocusStopInteractWithTrain,
            currenciesNeededAtPoint: (args["currenciesNeededAtPoint"] === undefined) ? emptyArrayFunction : args["currenciesNeededAtPoint"],
            currenciesMadeAtPoint: impulseFocusStopCurrenciesMadeAtPoint,
            isDisabled: false,
            currenciesExistAtPoint: currenciesExistAtPoint,
            updateCurrencyDictWithStop: updateCurrencyDictWithStop,
            isPathEnd: (args["isPathEnd"] === undefined) ? false : args["isPathEnd"],
            impulseCurrency: "impulse",
            checkFocusStopDisabled: checkFocusStopDisabled,
            requiredOpinions: (args["requiredOpinions"] === undefined) ? [] : args["requiredOpinions"],
            enableChecks: (args["enableChecks"] === undefined) ? [] : args["enableChecks"]
        };
    },
    angerResourceModifier: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.angerResourceModifier)));
        return ecs.resourceModifier(args);
    },
    selfExtensionRender: function(args){
        return {
            priority: 6,
            renderState: "default",
            render: selfExtensionRender
        };
    },
    dateAction_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateAction_personB)));
        return ecs.personCurrencyAction(args);
    },
    nastyCommentMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.nastyCommentMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    difficultySettingComponent: function(args){
        return {
            difficulty: 0
        };
    },
    jobQuotaGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.jobQuotaGoal)));
        return ecs.worldGoal(args);
    },
    notificationComponent: function(args){
        if (args.notificationFunctions === undefined){ throw "ECS fail " + args.key + " missing notificationFunctions"; }
        return {
            notificationFunctions: args['notificationFunctions'],
            serializeComponent: ecs.notificationSerializeComponent(args)
        };
    },
    notificationSerializeComponent: function(args){
        return {
            serialize: args['serialize'],
            postSerialize: notificationSerialize
        };
    },
    pointCounter: function(args){
        return {
            pointCounterComponent: ecs.pointCounterComponent(args),
            notificationComponents: [ecs.pointCounterNotificationComponent(args)]
        };
    },
    pathMaker: function(args){
        return {
            replaceMarker: ecs.pathMakerReplaceMarker(args)
        };
    },
    gainControl_fear: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.gainControl_fear)));
        return ecs.gainControlMenu(args);
    },
    buyRingGoalConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.buyRingGoalConstruction)));
        return ecs.construction(args);
    },
    sourceFocusPathStopComponent: function(args){
        return {
            interactWithTrain: falseFunction,
            currenciesNeededAtPoint: sourceCurrenciesNeededAtPoint,
            currenciesMadeAtPoint: (args["currenciesMadeAtPoint"] === undefined) ? emptyArrayFunction : args["currenciesMadeAtPoint"],
            isDisabled: false,
            currenciesExistAtPoint: currenciesExistAtPoint,
            updateCurrencyDictWithStop: updateCurrencyDictWithStop,
            isPathEnd: (args["isPathEnd"] === undefined) ? false : args["isPathEnd"],
            checkFocusStopDisabled: checkFocusStopDisabled,
            requiredOpinions: (args["requiredOpinions"] === undefined) ? [] : args["requiredOpinions"],
            enableChecks: (args["enableChecks"] === undefined) ? [] : args["enableChecks"]
        };
    },
    contactAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.contactAction)));
        return ecs.currencyAction(args);
    },
    carryTrainTickComponent: function(args){
        return {
            tick: carryTrainTick
        };
    },
    dayPanelRenderComponent: function(args){
        return {
            flashTimer: 0,
            dayActions: undefined,
            insetColor: "#000000",
            fullAnimationTime: 0.1,
            yTextOffset: 5,
            backImage: "opaqueBack",
            displayGameStage: undefined,
            drawImageButton: drawImageButton,
            animatingTime: 0.1,
            priority: 19,
            render: dayPanelRender,
            drawPanelButton: drawPanelButton,
            snapPanelPos: snapPanelPos,
            showPausingEntity: showPausingEntity,
            renderTime: 0,
            textColor: "#000000",
            renderState: "default",
            specificRender: args['specificRender'],
            drawHelpButton: drawHelpButton
        };
    },
    homeThoughtMineButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.homeThoughtMineButton)));
        return ecs.resourceButton(args);
    },
    checkQuality: function(args){
        return {
            tickComponent: ecs.checkQualityTickComponent(args)
        };
    },
    cursorRenderComponent: function(args){
        return {
            priority: 19,
            renderState: "default",
            render: cursorRender
        };
    },
    spendTimeGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.spendTimeGoal)));
        return ecs.personGoal(args);
    },
    junction: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            junctionComponent: ecs.junctionComponent(args),
            positionComponent: ecs.positionComponent(args)
        };
    },
    disavowAngerMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.disavowAngerMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    defineSelfMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.defineSelfMine)));
        return ecs.emotionResource(args);
    },
    spell: function(args){
        return {
            identityComponent: ecs.identityComponent(args),
            rendererComponents: [ecs.spellRenderComponent(args)],
            spellComponent: ecs.spellComponent(args)
        };
    },
    tutorialWaitForEnergyMine: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tutorialTextComponent: ecs.tutorialWaitForEnergyMineComponent(args)
        };
    },
    trackActionables: function(args){
        return {
            execute: trackActionables
        };
    },
    sourceRenderComponent: function(args){
        return {
            priority: 5,
            image: args['image'],
            renderState: "default",
            render: imageRender
        };
    },
    resourceComponent: function(args){
        if (args.resourceKeys === undefined){ throw "ECS fail " + args.key + " missing resourceKeys"; }
        return {
            resourceCount: (args["resourceCount"] === undefined) ? undefined : args["resourceCount"],
            ticks: 2,
            impulseTicks: 10,
            currenciesAtResource: currenciesAtResource,
            resourceKeys: args['resourceKeys'],
            resourceEntities: (args["resourceEntities"] === undefined) ? undefined : args["resourceEntities"]
        };
    },
    towerFlash: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            rendererComponents: [ecs.towerFlashRenderComponent(args)],
            positionComponent: ecs.positionComponent(args)
        };
    },
    entityPanelComponent: function(args){
        if (args.sourceId === undefined){ throw "ECS fail " + args.key + " missing sourceId"; }
        return {
            panelButtons: [],
            sourceId: args['sourceId'],
            expandPanel: expandPanel,
            minimized: true,
            canClose: true,
            closed: true,
            ignoreWhenMinimized: true
        };
    },
    disavowAnger: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.disavowAnger)));
        return ecs.emotionSource(args);
    },
    resource: function(args){
        args["touchable"] = true;
        args["canExpandInto"] = false;
        return {
            interactionComponent: ecs.interactionComponent(args),
            junctionComponent: ecs.junctionComponent(args),
            rendererComponents: [ecs.pathStopRenderComponent(args)],
            focusTickResponseComponent: ecs.resourceFocusTickResponseComponent(args),
            managedComponent: ecs.managedComponent(args),
            createExecutables: [ecs.makeExtraPathsExec(args)],
            focusPathStopComponent: ecs.resourceFocusPathStopComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            killExecutables: [ecs.killResourceCurrencies(args)],
            descriptionComponent: ecs.descriptionComponent(args),
            resourceComponent: ecs.resourceComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    workerRequireComponent: function(args){
        if (args.workerRequired === undefined){ throw "ECS fail " + args.key + " missing workerRequired"; }
        return {
            workerRequired: args['workerRequired'],
            workingTicks: 0
        };
    },
    frustrationMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.frustrationMine)));
        return ecs.memoryResource(args);
    },
    day: function(args){
        return {
            dayCounterComponent: ecs.dayCounterComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            killExecutables: [ecs.dayKillExec(args)]
        };
    },
    gameStateEntity: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.gameStateEntity)));
        return {
            rendererSystemComponent: ecs.rendererSystemComponent(args),
            uniqueComponent: ecs.assertUniqueComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    prepareButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.prepareButton)));
        return ecs.memoryTowerButton(args);
    },
    memory_learnSad: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_learnSad)));
        return ecs.memory(args);
    },
    trainPathComponent: function(args){
        if (args.trainStartId === undefined){ throw "ECS fail " + args.key + " missing trainStartId"; }
        if (args.trainStops === undefined){ throw "ECS fail " + args.key + " missing trainStops"; }
        return {
            makeTrain: makeTrain,
            trainId: undefined,
            trainStops: args['trainStops'],
            killTrainPath: undefined,
            stoppable: (args["stoppable"] === undefined) ? true : args["stoppable"],
            trainKey: PARAMETERS.train,
            canMakeTrain: canMakeTrain,
            requiresFullPath: (args["requiresFullPath"] === undefined) ? true : args["requiresFullPath"],
            getKillTrainPath: getKillTrainPath,
            baseMakeTrain: makeTrain,
            trainStartId: args['trainStartId'],
            paused: false,
            allPaths: [],
            trainColor: undefined
        };
    },
    tutorialIncludeWillComponent: function(args){
        return {
            entityToMark: args['entityToMark'],
            tutorialDone: tutorialTextTutorialDone,
            checkTutorialUiKey: checkTutorialIncludeWill,
            nextEntities: (args["nextEntities"] === undefined) ? undefined : args["nextEntities"]
        };
    },
    tutorialTextWakeUpPanel2: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialTextWakeUpPanel2)));
        return ecs.menuTutorialText(args);
    },
    buildPanelAbsolutePositionComponent: function(args){
        return {
            y: 678,
            x: 850,
            size: [174, 90]
        };
    },
    proposeGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.proposeGoal)));
        return ecs.personGoal(args);
    },
    randomOpenPositionExec: function(args){
        return {
            execute: randomOpenPositionExec,
            startEntityKey: (args["startEntityKey"] === undefined) ? undefined : args["startEntityKey"],
            startPos: (args.source && args.source.positionComponent) ? [args.source.positionComponent.x, args.source.positionComponent.y] : undefined,
            minSelfDist: (args["minSelfDist"] === undefined) ? 0 : args["minSelfDist"]
        };
    },
    setFlagExec: function(args){
        if (args.flag === undefined){ throw "ECS fail " + args.key + " missing flag"; }
        return {
            flagValue: (args["flagValue"] === undefined) ? true : args["flagValue"],
            execute: setFlagExec,
            flag: args['flag']
        };
    },
    sin: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.sin)));
        return ecs.currency(args);
    },
    paintAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.paintAction)));
        return ecs.currencyAction(args);
    },
    planTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.planTowerConstruction)));
        return ecs.construction(args);
    },
    memoryTower: function(args){
        args["touchable"] = true;
        args["canExpandInto"] = false;
        args["isPathEnd"] = (args["isPathEnd"] === undefined) ? (args.damageMultipliers ? true : false) : args["isPathEnd"];
        args["tags"] = (args["tags"] === undefined) ? [args.key] : args["tags"];
        return {
            junctionComponent: ecs.junctionComponent(args),
            rendererComponents: [ecs.pathStopRenderComponent(args), ecs.damageRenderComponent(args)],
            focusTickResponseComponent: ecs.towerFocusTickResponseComponent(args),
            managedComponent: ecs.managedComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            damageComponent: (args.damageMultipliers) ? ecs.damageComponent(args) : undefined,
            healthModifyComponent: ecs.healthModifyComponent(args),
            createExecutables: [ecs.makeExtraPathsExec(args)].concat([ecs.towerCreateExec(args)]),
            focusPathStopComponent: ecs.towerFocusPathStopComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            tickComponent: ecs.towerTickComponent(args),
            towerComponent: ecs.towerComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    buyRingMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.buyRingMenu)));
        return ecs.goalCompleteMenu(args);
    },
    allBuildingPanel: function(args){
        args["y"] = 5;
        args["x"] = (width - 800) / 2;
        args["key"] = PARAMETERS.allBuildingPanel;
        args["size"] = [800, 300];
        return {
            rendererComponents: [ecs.allBuildingPanelRenderComponent(args)],
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            uiClickComponent: ecs.panelUiClickComponent(args),
            panelComponent: ecs.panelComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    cleanPrideCheck: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cleanPrideCheck)));
        return ecs.notifyEntityMaker(args);
    },
    makeCurrencyRequest: function(args){
        if (args.currencyRequested === undefined){ throw "ECS fail " + args.key + " missing currencyRequested"; }
        return {
            execute: makeCurrencyRequest,
            requestKey: PARAMETERS.currencyRequest,
            currencyRequested: args['currencyRequested']
        };
    },
    socialMine_personDConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.socialMine_personDConstruction)));
        return ecs.construction(args);
    },
    bombPrep2Menu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.bombPrep2Menu)));
        return ecs.goalCompleteMenu(args);
    },
    killCurrencyExec: function(args){
        if (args.currencyToKill === undefined){ throw "ECS fail " + args.key + " missing currencyToKill"; }
        return {
            currencyToKill: args['currencyToKill'],
            execute: killCurrencyExec
        };
    },
    calmDownMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.calmDownMenu)));
        return ecs.goalCompleteMenu(args);
    },
    adultPathSelf: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.adultPathSelf)));
        return ecs.pathMaker(args);
    },
    missionGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.missionGoal)));
        return ecs.worldGoal(args);
    },
    menuTutorialTextRenderComponent: function(args){
        return {
            shownOnce: false,
            render: menuTutorialTextRender,
            text: args['text'],
            menuEntity: undefined,
            yOffset: (args["yOffset"] === undefined) ? 0 : args["yOffset"],
            priority: 24,
            entityToMark: args['entityToMark'],
            renderState: "default",
            shouldDrawArrow: (args["shouldDrawArrow"] === undefined) ? true : args["shouldDrawArrow"],
            hasDrawn: false,
            drawArrow: tutorialTextDrawArrow,
            xOffset: (args["xOffset"] === undefined) ? 100 : args["xOffset"]
        };
    },
    instantEmotionResourceTickComponent: function(args){
        return {
            tick: instantEmotionResourceTick
        };
    },
    killPersonGoals: function(args){
        return {
            execute: killPersonGoals
        };
    },
    currencyAction: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            identityComponent: ecs.identityComponent(args),
            currencyActionComponent: ecs.currencyActionComponent(args)
        };
    },
    workShiftGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.workShiftGoal)));
        return ecs.worldGoal(args);
    },
    risingText: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            rendererComponents: [ecs.risingTextRenderComponent(args)]
        };
    },
    progressRenderComponent: function(args){
        return {
            priority: 10,
            progressX: (args["progressX"] === undefined) ? 20 : args["progressX"],
            progressY: (args["progressY"] === undefined) ? 27 : args["progressY"],
            renderState: "default",
            render: progressRender
        };
    },
    demolishPathRenderComponent: function(args){
        return {
            priority: 3,
            demolishPathButtonFunction: undefined,
            renderState: "default",
            render: demolishPathButtonRender
        };
    },
    consequenceSad: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.consequenceSad)));
        return ecs.emotionSource(args);
    },
    tutorialFinishTrainComponent: function(args){
        return {
            entityToMark: args['entityToMark'],
            tutorialDone: tutorialTextTutorialDone,
            checkTutorialUiKey: checkTutorialFinishTrain,
            nextEntities: (args["nextEntities"] === undefined) ? undefined : args["nextEntities"]
        };
    },
    empathizeAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.empathizeAction)));
        return ecs.currencyAction(args);
    },
    rechargeAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.rechargeAction)));
        return ecs.currencyAction(args);
    },
    confessAction_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confessAction_personB)));
        return ecs.personCurrencyAction(args);
    },
    menuModifierComponent: function(args){
        return {
            completeText: (args["completeText"] === undefined) ? undefined : args["completeText"],
            name: (args["name"] === undefined) ? undefined : args["name"]
        };
    },
    driveButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.driveButton)));
        return ecs.towerButton(args);
    },
    dateFail: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateFail)));
        return ecs.emotionSource(args);
    },
    notifyComponent: function(args){
        if (args.notificationKey === undefined){ throw "ECS fail " + args.key + " missing notificationKey"; }
        if (args.notificationArgs === undefined){ throw "ECS fail " + args.key + " missing notificationArgs"; }
        return {
            notifyEntityKey: (args["notifyEntityKey"] === undefined) ? undefined : args["notifyEntityKey"],
            notificationKey: args['notificationKey'],
            notificationArgs: args['notificationArgs']
        };
    },
    flagBranch: function(args){
        return {
            replaceMarker: ecs.flagBranchReplaceMarker(args)
        };
    },
    chapter6Menu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chapter6Menu)));
        return ecs.gameStageStartMenu(args);
    },
    memoryEmotionSource: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            emotionSourceComponent: ecs.emotionSourceComponent(args),
            createExecutables: [ecs.emotionSourceCreateExec(args), ecs.killEntity(args)]
        };
    },
    towerControlComponent: function(args){
        if (args.entityCost === undefined){ throw "ECS fail " + args.key + " missing entityCost"; }
        if (args.entityKey === undefined){ throw "ECS fail " + args.key + " missing entityKey"; }
        if (args.constructionKey === undefined){ throw "ECS fail " + args.key + " missing constructionKey"; }
        if (args.limitingKeys === undefined){ throw "ECS fail " + args.key + " missing limitingKeys"; }
        return {
            aiVal: (args["aiVal"] === undefined) ? 0 : args["aiVal"],
            constructionKey: args['constructionKey'],
            checkMakePos: checkTowerMakePos,
            isNew: true,
            currenciesMade: (args["currenciesMade"] === undefined) ? [] : args["currenciesMade"],
            canMakeEntity: canBuildTower,
            entityKey: args['entityKey'],
            limitingKeys: args['limitingKeys'],
            wrappedFunction: undefined,
            managerKey: (args["managerKey"] === undefined) ? args.sourceKey : args["managerKey"],
            makeEntity: buildTower,
            getAiVal: controlComponentGetAiVal,
            getWrappedFunction: getWrappedFunction,
            maxTowerCount: (args["maxTowerCount"] === undefined) ? 2 : args["maxTowerCount"],
            entityCost: args['entityCost']
        };
    },
    memory_lifeThreat: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_lifeThreat)));
        return ecs.memory(args);
    },
    randomIfNoneReplaceMarker: function(args){
        if (args.keys === undefined){ throw "ECS fail " + args.key + " missing keys"; }
        return {
            keys: args['keys'],
            getReplaceKeys: randomIfNoneGetReplaceKeys
        };
    },
    disavowAngerMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.disavowAngerMine)));
        return ecs.emotionResource(args);
    },
    focusRenderComponent: function(args){
        return {
            priority: 9,
            renderState: "default",
            render: focusRender
        };
    },
    damamgeUpgradeCheckExec: function(args){
        if (args.towerKey === undefined){ throw "ECS fail " + args.key + " missing towerKey"; }
        if (args.flag === undefined){ throw "ECS fail " + args.key + " missing flag"; }
        return {
            entitiesToKill: (args["entitiesToKill"] === undefined) ? [] : args["entitiesToKill"],
            maxUpgradeCount: (args["maxUpgradeCount"] === undefined) ? 3 : args["maxUpgradeCount"],
            execute: damamgeUpgradeCheckExec,
            towerKey: args['towerKey'],
            flag: args['flag']
        };
    },
    killMotherland: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.killMotherland)));
        return ecs.killTrait(args);
    },
    dateGreat: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateGreat)));
        return ecs.emotionSource(args);
    },
    insecure: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.insecure)));
        return ecs.opinion(args);
    },
    randomMenuComponent: function(args){
        if (args.goodCount === undefined){ throw "ECS fail " + args.key + " missing goodCount"; }
        if (args.badCount === undefined){ throw "ECS fail " + args.key + " missing badCount"; }
        if (args.goodResult === undefined){ throw "ECS fail " + args.key + " missing goodResult"; }
        return {
            goodResult: args['goodResult'],
            goodCount: args['goodCount'],
            badCount: args['badCount']
        };
    },
    timeForceComponent: function(args){
        return {
            ticksPerTick: _.last(PARAMETERS.ticksPerTick)
        };
    },
    changeOpinionExec: function(args){
        return {
            execute: changeOpinion,
            opinionToAdd: (args["opinionToAdd"] === undefined) ? undefined : args["opinionToAdd"],
            opinionToRemove: (args["opinionToRemove"] === undefined) ? undefined : args["opinionToRemove"]
        };
    },
    impressMine_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.impressMine_personB)));
        return ecs.personResource(args);
    },
    impressMine_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.impressMine_personD)));
        return ecs.personResource(args);
    },
    addRandomPersonToMap: function(args){
        return {
            execute: addRandomPersonToMap
        };
    },
    creepDeath: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            rendererComponents: [ecs.creepDeathRenderComponent(args)]
        };
    },
    angerMap: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.angerMap)));
        return ecs.emotionResourceMap(args);
    },
    personHeldComponent: function(args){
        return {
            personHeld: (args["personHeld"] === undefined) ? undefined : args["personHeld"]
        };
    },
    steamAchievement: function(args){
        return {
            steamAchievementComponent: ecs.steamAchievementComponent(args),
            notificationComponents: [ecs.steamAchievementNotificationComponent(args)]
        };
    },
    nastyComment: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.nastyComment)));
        return ecs.emotionSource(args);
    },
    gameStageStartMenu: function(args){
        args["y"] = 5;
        args["x"] = width / 2 - 325;
        args["size"] = [650, 600];
        return {
            rendererComponents: [ecs.goalCompleteMenuRender(args)],
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            uiClickComponent: ecs.panelUiClickComponent(args),
            createExecutables: [ecs.goalCompleteMenuCreateExec(args)],
            goalCompleteMenuComponent: ecs.goalCompleteMenuComponent(args),
            panelComponent: ecs.goalCompleteMenuPanelComponent(args),
            pauseComponent: ecs.pauseComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            rebuildMenuComponent: ecs.rebuildMenuComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    choreButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.choreButton)));
        return ecs.towerButton(args);
    },
    tutorialTextEnergy: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialTextEnergy)));
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tutorialTextComponent: ecs.tutorialIncludeWillComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            rendererComponents: [ecs.tutorialTextRenderComponent(args)]
        };
    },
    imageRenderComponent: function(args){
        if (args.image === undefined){ throw "ECS fail " + args.key + " missing image"; }
        return {
            priority: 6,
            image: args['image'],
            renderState: "default",
            render: imageRender
        };
    },
    dailyEntityMaker: function(args){
        return {
            dailyEntityMakerComponent: ecs.dailyEntityMakerComponent(args),
            createExecutables: [ecs.randomOpenPositionExec(args)],
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            dayResponseComponent: ecs.dailyEntityDayResponseComponent(args),
            playerMadeComponent: ecs.fixedPlayerMadeComponent(args)
        };
    },
    meetPersonButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.meetPersonButton)));
        return ecs.goalButton(args);
    },
    empathizeTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.empathizeTower)));
        return ecs.memoryTower(args);
    },
    meetPersonGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.meetPersonGoal)));
        args["touchable"] = true;
        args["particleKey"] = PARAMETERS.newParticle;
        args["canExpandInto"] = false;
        args["playerKillable"] = !!(args.constructed);
        args["killOnTransform"] = (args["killOnTransform"] === undefined) ? true : args["killOnTransform"];
        return {
            uiActionComponent: ecs.buildingUiActionComponent(args),
            goalTrackComponent: ecs.meetPersonWorldGoalTrackComponent(args),
            createExecutables: [ecs.checkManagedComponentExec(args), (args.randomPosition) ? ecs.randomOpenPositionExec(args) : undefined, ecs.makeExtraPathsExec(args), ecs.chkForPlayerInterrupt(args), ecs.buildingConnectExec(args), (args.timeToDeath) ? ecs.timedDifficultyModifyExec(args) : undefined],
            timedDeathComponent: (args.timeToDeath) ? ecs.worldGoalTimedDeathComponent(args) : undefined,
            uiDataComponent: ecs.uiDataComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            rendererComponents: [ecs.worldGoalRenderComponent(args), ecs.progressRenderComponent(args), ecs.timedDeathRenderComponent(args)],
            focusTickResponseComponent: (args.aliveEntities) ? ecs.aliveMakeComponent(args) : undefined,
            managedComponent: (args.sourceTrait) ? ecs.managedComponent(args) : undefined,
            flipComponent: (args.flipEntity) ? ecs.flipComponent(args) : undefined,
            worldGoalComponent: ecs.worldGoalComponent(args),
            focusPathStopComponent: ecs.worldGoalFocusPathStopComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            positionComponent: ecs.positionComponent(args),
            notificationComponents: [ecs.worldGoalNotificationComponent(args)],
            junctionComponent: ecs.junctionComponent(args),
            pauseComponent: ecs.pauseComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    hitRenderComponent: function(args){
        return {
            priority: 9,
            wasHit: false,
            renderState: "default",
            render: hitRender
        };
    },
    rechargeTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.rechargeTower)));
        return ecs.memoryTower(args);
    },
    makeExtraPathsExec: function(args){
        return {
            execute: makeExtraPathsExec
        };
    },
    randomIfNone: function(args){
        return {
            replaceMarker: ecs.randomIfNoneReplaceMarker(args)
        };
    },
    tutorialText: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tutorialTextComponent: ecs.tutorialTextComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            rendererComponents: [ecs.tutorialTextRenderComponent(args)]
        };
    },
    parkPrepMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.parkPrepMenu)));
        return ecs.goalCompleteMenu(args);
    },
    disavowSadness: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.disavowSadness)));
        return ecs.emotionSource(args);
    },
    chessAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chessAction)));
        return ecs.currencyAction(args);
    },
    resourceModifierRenderComponent: function(args){
        return {
            render: towerTickModifierRender,
            image: args['image'],
            priority: 11,
            key: args['key'],
            getTowerForTickModifierRender: resourceModifierGetTowerForTickModifierRender,
            renderState: "default"
        };
    },
    towerFocusPathStopComponent: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.towerFocusPathStopComponent)));
        return {
            interactWithTrain: towerInteractWithTrain,
            currenciesNeededAtPoint: towerCurrenciesNeededAtPoint,
            currenciesMadeAtPoint: towerCurrenciesMadeAtPoint,
            isDisabled: false,
            currenciesExistAtPoint: currenciesExistAtPoint,
            updateCurrencyDictWithStop: updateCurrencyDictWithStop,
            isPathEnd: (args["isPathEnd"] === undefined) ? false : args["isPathEnd"],
            convertExecutables: [ecs.makeParticleEffect(args)],
            checkFocusStopDisabled: checkFocusStopDisabled,
            risingTextKey: "risingText",
            requiredOpinions: (args["requiredOpinions"] === undefined) ? [] : args["requiredOpinions"],
            enableChecks: (args["enableChecks"] === undefined) ? [] : args["enableChecks"]
        };
    },
    programming: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.programming)));
        return ecs.emotionSource(args);
    },
    emotionResourceDifficultyExec: function(args){
        return {
            execute: emotionResourceDifficultyExec
        };
    },
    flashback_angerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flashback_angerConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    threatMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.threatMine)));
        return ecs.emotionResource(args);
    },
    resourceFocusPathStopComponent: function(args){
        return {
            interactWithTrain: resourceInteractWithTrain,
            currenciesNeededAtPoint: (args["currenciesNeededAtPoint"] === undefined) ? emptyArrayFunction : args["currenciesNeededAtPoint"],
            currenciesMadeAtPoint: resourceCurrenciesMadeAtPoint,
            isDisabled: false,
            currenciesExistAtPoint: currenciesExistAtPoint,
            updateCurrencyDictWithStop: updateCurrencyDictWithStop,
            isPathEnd: (args["isPathEnd"] === undefined) ? false : args["isPathEnd"],
            checkFocusStopDisabled: checkFocusStopDisabled,
            requiredOpinions: (args["requiredOpinions"] === undefined) ? [] : args["requiredOpinions"],
            enableChecks: (args["enableChecks"] === undefined) ? [] : args["enableChecks"]
        };
    },
    hasProject: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.hasProject)));
        return ecs.opinion(args);
    },
    threat: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.threat)));
        return ecs.emotionSource(args);
    },
    adult_ext: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.adult_ext)));
        return ecs.sourceExtension(args);
    },
    emotionSourceConstruction: function(args){
        args["touchable"] = true;
        args["canExpandInto"] = false;
        args["killWithoutPath"] = true;
        return {
            focusPathStopComponent: ecs.constructionFocusPathStopComponent(args),
            rendererComponents: [ecs.constructionRenderComponent(args), ecs.progressRenderComponent(args)],
            personHeldComponent: ecs.personHeldComponent(args),
            managedComponent: ecs.managedComponent(args),
            constructionComponent: ecs.memoryConstructionComponent(args),
            createExecutables: [ecs.emotionSourceConstructionCreateExec(args)],
            junctionComponent: ecs.junctionComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            tickComponent: ecs.constructionTickComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.fixedPlayerMadeComponent(args)
        };
    },
    panelUiClickComponent: function(args){
        return {
            uiClick: panelUiClick
        };
    },
    currencyActionComponent: function(args){
        if (args.towerKey === undefined){ throw "ECS fail " + args.key + " missing towerKey"; }
        return {
            nextIds: [],
            towerKey: args['towerKey'],
            entitiesOnConvert: (args["entitiesOnConvert"] === undefined) ? [] : args["entitiesOnConvert"],
            currenciesRequired: (args["currenciesRequired"] === undefined) ? [] : args["currenciesRequired"],
            towerIds: [],
            currenciesToMake: (args["currenciesToMake"] === undefined) ? [] : args["currenciesToMake"]
        };
    },
    buildingUiActionComponent: function(args){
        return {
            getUiActions: getUiActions,
            generateUiActions: entityGenerateUiActions,
            memoizedActions: undefined
        };
    },
    papersMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.papersMenu)));
        return ecs.goalCompleteMenu(args);
    },
    damageableComponent: function(args){
        if (args.emotion === undefined){ throw "ECS fail " + args.key + " missing emotion"; }
        if (args.emotionTags === undefined){ throw "ECS fail " + args.key + " missing emotionTags"; }
        return {
            emotion: args['emotion'],
            traits: (args["traits"] === undefined) ? [] : args["traits"],
            getAll: getAllEmotionParts,
            emotionTags: args['emotionTags'],
            damageEffects: []
        };
    },
    choreTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.choreTower)));
        return ecs.tower(args);
    },
    flirtAction_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flirtAction_personB)));
        return ecs.personCurrencyAction(args);
    },
    flashback_sadness: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flashback_sadness)));
        return ecs.emotionResource(args);
    },
    sympathy: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.sympathy)));
        return ecs.currency(args);
    },
    personBPathCity: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.personBPathCity)));
        return ecs.pathMaker(args);
    },
    entityManagerComponent: function(args){
        return {
            findFreeManagedSpace: findFreeManagedSpace,
            disabled: false,
            hasNew: false,
            checkForNewManaged: checkForNewManaged,
            canMakeManagedEntity: canMakeManagedEntity
        };
    },
    memory_betrayal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_betrayal)));
        return ecs.memory(args);
    },
    steamMarriage: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.steamMarriage)));
        return ecs.steamAchievement(args);
    },
    exploreMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.exploreMenu)));
        return ecs.goalCompleteMenu(args);
    },
    cleanHouseMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cleanHouseMenu)));
        return ecs.goalCompleteMenu(args);
    },
    makeEntityComponent: function(args){
        return {
            execute: executeMakeEntities,
            entitiesToMake: (args["entitiesToMake"] === undefined) ? [] : args["entitiesToMake"],
            adjacent: (args["adjacent"] === undefined) ? false : args["adjacent"]
        };
    },
    city: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.city)));
        return ecs.trait(args);
    },
    clearTrait: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            createExecutables: [ecs.clearTraitExec(args), ecs.killEntity(args)]
        };
    },
    proposeMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.proposeMenu)));
        return ecs.goalCompleteMenu(args);
    },
    escapeMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.escapeMenu)));
        return ecs.goalCompleteMenu(args);
    },
    meetPersonMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.meetPersonMenu)));
        return ecs.goalCompleteMenu(args);
    },
    dailyEntityMakerComponent: function(args){
        if (args.dayStartEntity === undefined){ throw "ECS fail " + args.key + " missing dayStartEntity"; }
        if (args.dayCount === undefined){ throw "ECS fail " + args.key + " missing dayCount"; }
        return {
            dayCount: args['dayCount'],
            dayStartEntity: args['dayStartEntity'],
            daysToMake: args['dayCount']
        };
    },
    personDPathMotherland: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.personDPathMotherland)));
        return ecs.pathMaker(args);
    },
    nastyCommentMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.nastyCommentMine)));
        return ecs.emotionResource(args);
    },
    memoryStopGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memoryStopGoal)));
        args["touchable"] = true;
        args["particleKey"] = PARAMETERS.newParticle;
        args["canExpandInto"] = false;
        args["killOnTimeOut"] = [args.sourceKey];
        args["playerKillable"] = !!(args.constructed);
        args["killOnTransform"] = (args["killOnTransform"] === undefined) ? true : args["killOnTransform"];
        return {
            uiActionComponent: ecs.buildingUiActionComponent(args),
            goalTrackComponent: ecs.checkWorldGoalComponent(args),
            createExecutables: [ecs.checkManagedComponentExec(args), (args.randomPosition) ? ecs.randomOpenPositionExec(args) : undefined, ecs.makeExtraPathsExec(args), ecs.chkForPlayerInterrupt(args), ecs.buildingConnectExec(args), (args.timeToDeath) ? ecs.timedDifficultyModifyExec(args) : undefined],
            timedDeathComponent: (args.timeToDeath) ? ecs.worldGoalTimedDeathComponent(args) : undefined,
            uiDataComponent: ecs.uiDataComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            rendererComponents: [ecs.worldGoalRenderComponent(args), ecs.progressRenderComponent(args), ecs.timedDeathRenderComponent(args)],
            focusTickResponseComponent: (args.aliveEntities) ? ecs.aliveMakeComponent(args) : undefined,
            managedComponent: (args.sourceTrait) ? ecs.managedComponent(args) : undefined,
            flipComponent: (args.flipEntity) ? ecs.flipComponent(args) : undefined,
            worldGoalComponent: ecs.worldGoalComponent(args),
            focusPathStopComponent: ecs.worldGoalFocusPathStopComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            positionComponent: ecs.positionComponent(args),
            notificationComponents: [ecs.worldGoalNotificationComponent(args)],
            junctionComponent: ecs.junctionComponent(args),
            pauseComponent: ecs.pauseComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    tutorialTextRenderComponent: function(args){
        if (args.entityToMark === undefined){ throw "ECS fail " + args.key + " missing entityToMark"; }
        if (args.text === undefined){ throw "ECS fail " + args.key + " missing text"; }
        return {
            render: tutorialTextRender,
            text: args['text'],
            yOffset: (args["yOffset"] === undefined) ? 0 : args["yOffset"],
            priority: 24,
            entityToMark: args['entityToMark'],
            renderState: "default",
            drawArrow: tutorialTextDrawArrow,
            xOffset: (args["xOffset"] === undefined) ? 100 : args["xOffset"]
        };
    },
    personGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.personGoal)));
        args["particleKey"] = PARAMETERS.newParticle;
        args["description"] = (args.description && args.description[args.personHeld]) ? args.description[args.personHeld] : args.description;
        args["touchable"] = true;
        args["playerKillable"] = !!(args.constructed);
        args["startEntityKey"] = args.personHeld;
        args["canExpandInto"] = false;
        args["currencyRequested"] = derivePersonGoalCurrencies(args.currencyRequested, args.personHeld);
        args["killOnTransform"] = (args["killOnTransform"] === undefined) ? true : args["killOnTransform"];
        if (args.personHeld === undefined){ throw "ECS fail " + args.key + " missing personHeld"; }
        return {
            uiActionComponent: ecs.buildingUiActionComponent(args),
            goalTrackComponent: ecs.personGoalTrackComponent(args),
            createExecutables: [ecs.checkManagedComponentExec(args), (args.randomPosition) ? ecs.randomOpenPositionExec(args) : undefined, ecs.makeExtraPathsExec(args), ecs.chkForPlayerInterrupt(args), ecs.buildingConnectExec(args), (args.timeToDeath) ? ecs.timedDifficultyModifyExec(args) : undefined],
            timedDeathComponent: (args.timeToDeath) ? ecs.worldGoalTimedDeathComponent(args) : undefined,
            uiDataComponent: ecs.uiDataComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            rendererComponents: [ecs.worldGoalRenderComponent(args), ecs.progressRenderComponent(args), ecs.timedDeathRenderComponent(args)],
            focusTickResponseComponent: (args.aliveEntities) ? ecs.aliveMakeComponent(args) : undefined,
            managedComponent: (args.sourceTrait) ? ecs.managedComponent(args) : undefined,
            flipComponent: (args.flipEntity) ? ecs.flipComponent(args) : undefined,
            personHeldComponent: ecs.personHeldComponent(args),
            worldGoalComponent: ecs.worldGoalComponent(args),
            focusPathStopComponent: ecs.worldGoalFocusPathStopComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            positionComponent: ecs.positionComponent(args),
            notificationComponents: [ecs.worldGoalNotificationComponent(args)],
            junctionComponent: ecs.junctionComponent(args),
            pauseComponent: ecs.pauseComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    prideJoyMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.prideJoyMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    tutorialIncludeWill: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialIncludeWill)));
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tutorialTextComponent: ecs.tutorialIncludeWillComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            rendererComponents: [ecs.tutorialTextRenderComponent(args)]
        };
    },
    desireMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.desireMineConstruction)));
        return ecs.construction(args);
    },
    flareMemoryMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flareMemoryMenu)));
        return ecs.goalCompleteMenu(args);
    },
    openMenuMarkerRenderComponent: function(args){
        return {
            priority: 14,
            renderState: "default",
            render: openMenuMarkerRender
        };
    },
    friend: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.friend)));
        return ecs.opinion(args);
    },
    openMenuMarkerComponent: function(args){
        if (args.entityUid === undefined){ throw "ECS fail " + args.key + " missing entityUid"; }
        return {
            entityUid: args['entityUid']
        };
    },
    paintTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.paintTower)));
        return ecs.tower(args);
    },
    personGoalButton: function(args){
        args["managerKey"] = args.personHeld;
        args["subPanel"] = (args["subPanel"] === undefined) ? "Goals" : args["subPanel"];
        return {
            uiActionComponent: ecs.buttonUiActionComponent(args),
            buttonControlComponent: ecs.goalButtonControlComponent(args),
            personHeldComponent: ecs.personHeldComponent(args),
            createExecutables: [ecs.killIfFlagExec(args), ecs.markTraitNew(args)],
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            uiDataComponent: ecs.uiDataComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    activationMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.activationMenu)));
        return ecs.goalCompleteMenu(args);
    },
    pathConnectRenderComponent: function(args){
        return {
            priority: 4,
            timeDrawn: 0,
            renderState: "default",
            render: pathConnectRender
        };
    },
    chessTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chessTower)));
        return ecs.tower(args);
    },
    memory_dateGreat: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_dateGreat)));
        return ecs.memory(args);
    },
    entityReplaceMarker: function(args){
        return {
            entitiesToMake: (args["entitiesToMake"] === undefined) ? [] : args["entitiesToMake"],
            skipFlag: (args["skipFlag"] === undefined) ? undefined : args["skipFlag"],
            getReplaceKeys: entityReplaceGetReplaceKeys
        };
    },
    desireMineButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.desireMineButton)));
        return ecs.resourceButton(args);
    },
    readHomeMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.readHomeMenu)));
        return ecs.goalCompleteMenu(args);
    },
    memoryTowerButton: function(args){
        args["managerKey"] = args.sourceKey;
        args["multiTrait"] = true;
        args["subPanel"] = (args["subPanel"] === undefined) ? "Towers" : args["subPanel"];
        args["multiTrait"] = (args["multiTrait"] === undefined) ? false : args["multiTrait"];
        if (args.sourceKey === undefined){ throw "ECS fail " + args.key + " missing sourceKey"; }
        return {
            healthComponent: ecs.singleHealthComponent(args),
            isaacEntityComponent: ecs.memoryTowerButtonIssacEntityComponent(args),
            uiActionComponent: ecs.buttonUiActionComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            buttonControlComponent: ecs.towerControlComponent(args),
            createExecutables: [ecs.markTraitNew(args)],
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            killExecutables: [ecs.killTowerButtonExec(args)],
            descriptionComponent: ecs.descriptionComponent(args),
            uniqueComponent: (args.timeToDeath === undefined && !args.multiTrait) ? ecs.uniqueComponent(args) : undefined,
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    tutorialWaitForEnergyMineComponent: function(args){
        return {
            tutorialDone: tutorialWaitForEnergyDone,
            keyToCheck: "energyMine",
            nextEntities: "tutorialTextWakeUpFinish"
        };
    },
    buyRingCreateExec: function(args){
        return {
            keysToKill: ["crush", "dating", "meetPersonGoal"],
            execute: buyRingCreateExec
        };
    },
    flashback_fearConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flashback_fearConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    workerRequestComponent: function(args){
        return {
            needsWorker: trueFunction,
            calculateWorkerTicks: calculateWorkerTicks,
            workerRequested: args.sourceTrait + '_worker',
            workersActive: [],
            workerCount: 1,
            workerLife: 20
        };
    },
    gameStageDayResponseComponent: function(args){
        return {
            dayEnd: gameStageDayEnd,
            dayStart: (args["dayStart"] === undefined) ? undefined : args["dayStart"],
            daysLeft: 2,
            dayChange: (args["dayChange"] === undefined) ? undefined : args["dayChange"]
        };
    },
    dateTower_personBConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateTower_personBConstruction)));
        return ecs.construction(args);
    },
    mapSpawnComponent: function(args){
        if (args.mapSpawnKey === undefined){ throw "ECS fail " + args.key + " missing mapSpawnKey"; }
        return {
            mapSpawnPos: mapSpawnPos,
            mapPostSpawn: trueFunction,
            mapShouldSpawn: trueFunction,
            mapSpawn: mapSpawn,
            mapSpawnKey: args['mapSpawnKey']
        };
    },
    dailyEntityDayResponseComponent: function(args){
        return {
            dayStart: dailyEntityMake
        };
    },
    datingCreateExec: function(args){
        return {
            execute: datingCreate
        };
    },
    consequenceSadMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.consequenceSadMine)));
        return ecs.emotionResource(args);
    },
    pathButtonRenderComponent: function(args){
        return {
            priority: 1,
            renderState: "default",
            render: pathButtonRender,
            pathButtonFunction: undefined
        };
    },
    worldGoalRenderComponent: function(args){
        if (args.image === undefined){ throw "ECS fail " + args.key + " missing image"; }
        return {
            outlineImage: false,
            render: worldGoalRender,
            wobble: 0,
            image: args['image'],
            priority: 6,
            timeToBlink: 1,
            newImage: "newOutline",
            getWobble: getWobble,
            renderState: "default",
            renderTrainIcon: renderTrainIcon,
            outlineTime: 0
        };
    },
    pathStopRenderComponent: function(args){
        if (args.activeImage === undefined){ throw "ECS fail " + args.key + " missing activeImage"; }
        if (args.image === undefined){ throw "ECS fail " + args.key + " missing image"; }
        return {
            priority: 7,
            outlineImage: false,
            render: pathStopRender,
            activeImage: args['activeImage'],
            wobble: 0,
            image: args['image'],
            blinkingOutlineTime: 0,
            timeDrawn: 0,
            outlineTime: 0,
            blinkingOutlineImage: false,
            renderTime: 0.1,
            getWobble: getWobble,
            renderState: "default",
            renderTrainIcon: renderTrainIcon,
            isActive: false
        };
    },
    preparation: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.preparation)));
        return ecs.currency(args);
    },
    risingTextRenderComponent: function(args){
        if (args.text === undefined){ throw "ECS fail " + args.key + " missing text"; }
        if (args.effectPos === undefined){ throw "ECS fail " + args.key + " missing effectPos"; }
        return {
            effectTime: 0.5,
            render: risingTextRender,
            text: args['text'],
            effectPos: args['effectPos'],
            height: 100,
            priority: 10,
            currentHeight: 0,
            renderState: "default"
        };
    },
    paintButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.paintButton)));
        return ecs.towerButton(args);
    },
    impressMine_personDConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.impressMine_personDConstruction)));
        return ecs.construction(args);
    },
    healEntities: function(args){
        return {
            execute: healEntities,
            entitiesToHeal: (args["entitiesToHeal"] === undefined) ? {} : args["entitiesToHeal"]
        };
    },
    rechargeTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.rechargeTowerConstruction)));
        return ecs.memoryConstruction(args);
    },
    openMindGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.openMindGoal)));
        return ecs.worldGoal(args);
    },
    makeBuyRingGoal: function(args){
        return {
            replaceMarker: ecs.entityReplaceMarker(args),
            createExecutables: [ecs.makeBuyRingGoalCreate(args), ecs.makeEntityComponent(args), ecs.killEntity(args)]
        };
    },
    empathizeTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.empathizeTowerConstruction)));
        return ecs.memoryConstruction(args);
    },
    revertMemoryExec: function(args){
        if (args.sourceId === undefined){ throw "ECS fail " + args.key + " missing sourceId"; }
        return {
            sourceId: args['sourceId'],
            execute: revertMemoryExec
        };
    },
    askOutMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.askOutMenu)));
        return ecs.goalCompleteMenu(args);
    },
    goalButtonControlComponent: function(args){
        if (args.entityCost === undefined){ throw "ECS fail " + args.key + " missing entityCost"; }
        if (args.entityKey === undefined){ throw "ECS fail " + args.key + " missing entityKey"; }
        if (args.constructionKey === undefined){ throw "ECS fail " + args.key + " missing constructionKey"; }
        return {
            entityCost: args['entityCost'],
            aiVal: (args["aiVal"] === undefined) ? 0 : args["aiVal"],
            constructionKey: args['constructionKey'],
            checkMakePos: checkTowerMakePos,
            isNew: true,
            currenciesMade: (args["currenciesMade"] === undefined) ? [] : args["currenciesMade"],
            canMakeEntity: canMakeGoal,
            requiredEntities: (args["requiredEntities"] === undefined) ? [] : args["requiredEntities"],
            entityKey: args['entityKey'],
            wrappedFunction: undefined,
            disablingEntities: (args["disablingEntities"] === undefined) ? [] : args["disablingEntities"],
            managerKey: (args["managerKey"] === undefined) ? args.sourceKey : args["managerKey"],
            makeEntity: makeGoal,
            getAiVal: controlComponentGetAiVal,
            getWrappedFunction: getWrappedFunction,
            displayRiskIcon: (args["displayRiskIcon"] === undefined) ? false : args["displayRiskIcon"]
        };
    },
    towerComponent: function(args){
        return {
            possibleCurrencyActionForTower: possibleCurrencyActionForTower,
            storeCount: 3,
            canDamageTower: canDamageTower,
            currencyActionForTower: currencyActionForTower
        };
    },
    chapter5Menu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chapter5Menu)));
        return ecs.gameStageStartMenu(args);
    },
    tutorialTextAdultPanel: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialTextAdultPanel)));
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tutorialTextComponent: ecs.tutorialTextAdultPanelComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            rendererComponents: [ecs.menuTutorialTextRenderComponent(args)]
        };
    },
    makeGoalMenuExecutable: function(args){
        if (args.goalMenuKey === undefined){ throw "ECS fail " + args.key + " missing goalMenuKey"; }
        return {
            execute: makeGoalMenuExecute,
            goalMenuKey: args['goalMenuKey']
        };
    },
    fantasyMine_personBConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fantasyMine_personBConstruction)));
        return ecs.construction(args);
    },
    creepTickComponent: function(args){
        return {
            tick: creepTick,
            memoryChance: 0
        };
    },
    contact_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.contact_personB)));
        return ecs.personCurrency(args);
    },
    qualityResourceTickComponent: function(args){
        if (args.qualityThreshold === undefined){ throw "ECS fail " + args.key + " missing qualityThreshold"; }
        if (args.highQualityResources === undefined){ throw "ECS fail " + args.key + " missing highQualityResources"; }
        return {
            tick: qualityResourceTick,
            qualityThreshold: args['qualityThreshold'],
            highQualityResources: args['highQualityResources'],
            flipEntities: (args["flipEntities"] === undefined) ? [] : args["flipEntities"],
            lastQuality: args['quality'],
            lowQualityResources: args['resourceKeys']
        };
    },
    fadingMemory: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fadingMemory)));
        args["touchable"] = true;
        args["key"] = args.key + '_' + args.uid;
        args["health"] = (args["health"] === undefined) ? 2000 : args["health"];
        args["panelKey"] = (args["panelKey"] === undefined) ? PARAMETERS.traitPanel : args["panelKey"];
        return {
            uiActionComponent: ecs.buildingUiActionComponent(args),
            experienceComponent: ecs.experienceComponent(args),
            createExecutables: [ecs.builtSourceCreateExec(args), ecs.makeExtraPathsExec(args)],
            timedDeathComponent: ecs.timedDeathComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            entityManagerComponent: ecs.entityManagerComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            isaacEntityComponent: ecs.isaacEntityComponent(args),
            rendererComponents: [ecs.builtSourceRendererComponent(args)],
            focusTickResponseComponent: (args.aliveEntities) ? ecs.focusTickAliveMakerComponent(args) : undefined,
            menuCloseComponent: ecs.traitMenuCloseComponent(args),
            personHeldComponent: (args.personHeld) ? ecs.personHeldComponent(args) : undefined,
            clickComponent: ecs.fullMenuClickComponent(args),
            killExecutables: [ecs.killExtensions(args)],
            healthComponent: ecs.healthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            positionComponent: ecs.positionComponent(args),
            mapSpawnComponent: ecs.builtSourceMapSpawnComponent(args),
            fadingMemoryComponent: ecs.fadingMemoryComponent(args),
            junctionComponent: ecs.junctionComponent(args),
            playerMadeComponent: ecs.fixedPlayerMadeComponent(args)
        };
    },
    prepareAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.prepareAction)));
        return ecs.currencyAction(args);
    },
    anger: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.anger)));
        return ecs.creep(args);
    },
    qualityComponent: function(args){
        if (args.quality === undefined){ throw "ECS fail " + args.key + " missing quality"; }
        return {
            quality: args['quality']
        };
    },
    qualityRenderComponent: function(args){
        return {
            priority: 9,
            renderState: "default",
            render: qualityRender
        };
    },
    newParticle: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.newParticle)));
        return ecs.worldGoalParticleEffect(args);
    },
    social_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.social_personB)));
        return ecs.personCurrency(args);
    },
    workerRenderComponent: function(args){
        if (args.image === undefined){ throw "ECS fail " + args.key + " missing image"; }
        return {
            priority: 12,
            image: args['image'],
            renderState: "default",
            render: currencyRender,
            renderPos: undefined
        };
    },
    demolishButton: function(args){
        return {
            rendererComponents: [ecs.demolishButtonRenderComponent(args)]
        };
    },
    memory_consequenceSad: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_consequenceSad)));
        return ecs.memory(args);
    },
    experienceTrigger: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            experienceTriggerComponent: ecs.experienceTriggerComponent(args)
        };
    },
    settingsPanel: function(args){
        args["draggable"] = false;
        args["key"] = PARAMETERS.settingsPanel;
        args["size"] = [450, 400];
        return {
            rendererComponents: [ecs.settingsPanelRenderComponent(args)],
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            uiClickComponent: ecs.panelUiClickComponent(args),
            panelComponent: ecs.panelComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    disabledTowerRenderComponent: function(args){
        return {
            priority: 6,
            image: "disabledTower",
            renderState: "default",
            render: disabledTowerRender
        };
    },
    loseJobMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.loseJobMenu)));
        return ecs.goalCompleteMenu(args);
    },
    travelTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.travelTower)));
        return ecs.tower(args);
    },
    self: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.self)));
        return ecs.source(args);
    },
    currencyPathComponent: function(args){
        return {
            currencies: (args["currencies"] === undefined) ? [] : args["currencies"],
            buildingActions: (args["buildingActions"] === undefined) ? [] : args["buildingActions"]
        };
    },
    betrayalMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.betrayalMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    sourceExtensionClickComponent: function(args){
        return {
            onClick: sourceExtensionOnClick
        };
    },
    pointCounterComponent: function(args){
        return {
            points: 0
        };
    },
    constructionFocusPathStopComponent: function(args){
        return {
            currencyEntities: (args["currencyEntities"] === undefined) ? undefined : args["currencyEntities"],
            interactWithTrain: currencyRequestedInteractWithTrain,
            currencyEffect: (args["currencyEffect"] === undefined) ? undefined : args["currencyEffect"],
            currenciesNeededAtPoint: currencyRequestedCurrenciesNeededAtPoint,
            updateCurrencyDictWithStop: updateCurrencyDictWithStop,
            getCurrencyComponent: constructionGetCurrencyComponent,
            currenciesExistAtPoint: currenciesExistAtPoint,
            currenciesMadeAtPoint: (args["currenciesMadeAtPoint"] === undefined) ? emptyArrayFunction : args["currenciesMadeAtPoint"],
            isPathEnd: true,
            requiredOpinions: (args["requiredOpinions"] === undefined) ? [] : args["requiredOpinions"],
            percentComplete: currencyRequestedFocusPathStopPercentComplete,
            checkFocusStopDisabled: checkFocusStopDisabled,
            isDisabled: false,
            enableChecks: (args["enableChecks"] === undefined) ? [] : args["enableChecks"]
        };
    },
    daydreamMine_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.daydreamMine_personB)));
        return ecs.personResource(args);
    },
    disavowSadnessMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.disavowSadnessMine)));
        return ecs.emotionResource(args);
    },
    assertUniqueComponent: function(args){
        return {
            uniqueDuplicateEvent: assertUnique
        };
    },
    currencyRequestedFocusPathStopComponent: function(args){
        return {
            currencyEntities: (args["currencyEntities"] === undefined) ? undefined : args["currencyEntities"],
            interactWithTrain: currencyRequestedInteractWithTrain,
            currencyEffect: (args["currencyEffect"] === undefined) ? undefined : args["currencyEffect"],
            currenciesNeededAtPoint: currencyRequestedCurrenciesNeededAtPoint,
            updateCurrencyDictWithStop: updateCurrencyDictWithStop,
            getCurrencyComponent: trueFunction,
            currenciesExistAtPoint: currenciesExistAtPoint,
            currenciesMadeAtPoint: (args["currenciesMadeAtPoint"] === undefined) ? emptyArrayFunction : args["currenciesMadeAtPoint"],
            isPathEnd: true,
            requiredOpinions: (args["requiredOpinions"] === undefined) ? [] : args["requiredOpinions"],
            percentComplete: currencyRequestedFocusPathStopPercentComplete,
            checkFocusStopDisabled: checkFocusStopDisabled,
            isDisabled: false,
            enableChecks: (args["enableChecks"] === undefined) ? [] : args["enableChecks"]
        };
    },
    travelTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.travelTowerConstruction)));
        return ecs.construction(args);
    },
    notificationCurrencyComponent: function(args){
        if (args.notificationCurrency === undefined){ throw "ECS fail " + args.key + " missing notificationCurrency"; }
        if (args.currencyName === undefined){ throw "ECS fail " + args.key + " missing currencyName"; }
        return {
            notificationCurrency: args['notificationCurrency'],
            currencyName: args['currencyName']
        };
    },
    applyButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.applyButton)));
        return ecs.memoryTowerButton(args);
    },
    noFocusNotifierRenderComponent: function(args){
        return {
            priority: 16,
            render: noFocusNotifierRender
        };
    },
    disavowal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.disavowal)));
        return ecs.currency(args);
    },
    parkDateMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.parkDateMenu)));
        return ecs.goalCompleteMenu(args);
    },
    trainPathTowerActiveRender: function(args){
        return {
            priority: 3,
            renderState: "default",
            render: trainPathTowerActiveRender
        };
    },
    intelligentPathMotherland: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.intelligentPathMotherland)));
        return ecs.pathMaker(args);
    },
    industryMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.industryMineConstruction)));
        return ecs.construction(args);
    },
    lifeThreatMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.lifeThreatMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    buyTimeGoalConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.buyTimeGoalConstruction)));
        return ecs.construction(args);
    },
    restaurantPrepGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.restaurantPrepGoal)));
        return ecs.personGoal(args);
    },
    letter: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.letter)));
        return ecs.currency(args);
    },
    parkPrepGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.parkPrepGoal)));
        return ecs.personGoal(args);
    },
    flirtTower_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flirtTower_personD)));
        return ecs.personTower(args);
    },
    positionComponent: function(args){
        return {
            planeOfExistence: (args["planeOfExistence"] === undefined) ? undefined : args["planeOfExistence"],
            setPos: setPos,
            comparePos: compComparePos,
            canBuildAtPos: trueFunction,
            isPos: isPos,
            y: assertPosition(args.y, 1),
            x: assertPosition(args.x, 0),
            getAsPos: getAsPos
        };
    },
    flirtTower_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flirtTower_personB)));
        return ecs.personTower(args);
    },
    personalityComponent: function(args){
        args["knownPersonalityTraits"] = (args["knownPersonalityTraits"] === undefined) ? [] : args["knownPersonalityTraits"];
        args["unknownPersonalityTraits"] = (args["unknownPersonalityTraits"] === undefined) ? [] : args["unknownPersonalityTraits"];
        if (args.knownPersonalityTraits === undefined){ throw "ECS fail " + args.key + " missing knownPersonalityTraits"; }
        return {
            knownPersonalityTraits: args['knownPersonalityTraits'],
            heldOpinions: (args["heldOpinions"] === undefined) ? PARAMETERS.defaultHeldOpinions : args["heldOpinions"],
            getAll: getAllPersonality,
            addOpinionToPersonality: addOpinionToPersonality,
            personHistory: [],
            personalityTraits: args.unknownPersonalityTraits.concat(args.knownPersonalityTraits)
        };
    },
    frustrationMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.frustrationMineConstruction)));
        return ecs.memoryConstruction(args);
    },
    focusPathStopComponent: function(args){
        return {
            interactWithTrain: falseFunction,
            currenciesNeededAtPoint: (args["currenciesNeededAtPoint"] === undefined) ? emptyArrayFunction : args["currenciesNeededAtPoint"],
            currenciesMadeAtPoint: (args["currenciesMadeAtPoint"] === undefined) ? emptyArrayFunction : args["currenciesMadeAtPoint"],
            isDisabled: false,
            currenciesExistAtPoint: currenciesExistAtPoint,
            updateCurrencyDictWithStop: updateCurrencyDictWithStop,
            isPathEnd: (args["isPathEnd"] === undefined) ? false : args["isPathEnd"],
            checkFocusStopDisabled: checkFocusStopDisabled,
            requiredOpinions: (args["requiredOpinions"] === undefined) ? [] : args["requiredOpinions"],
            enableChecks: (args["enableChecks"] === undefined) ? [] : args["enableChecks"]
        };
    },
    focusPanelAbsolutePositionComponent: function(args){
        return {
            y: 678,
            x: 300,
            size: [300, 90]
        };
    },
    killResourceCurrencies: function(args){
        return {
            execute: killResourceCurrencies
        };
    },
    tutorialUnpause: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tutorialTextComponent: ecs.tutorialUnpauseTextComponent(args),
            rendererComponents: [ecs.tutorialUnpauseRenderComponent(args)]
        };
    },
    junctionRenderComponent: function(args){
        return {
            priority: 12,
            renderState: "default",
            render: junctionRender
        };
    },
    socialMine_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.socialMine_personB)));
        return ecs.personResource(args);
    },
    rendererComponent: function(args){
        if (args.render === undefined){ throw "ECS fail " + args.key + " missing render"; }
        if (args.priority === undefined){ throw "ECS fail " + args.key + " missing priority"; }
        return {
            priority: args['priority'],
            renderState: "default",
            render: args['render']
        };
    },
    healthBleedTickComponent: function(args){
        return {
            tick: healthBleedTick
        };
    },
    constructionRenderComponent: function(args){
        if (args.image === undefined){ throw "ECS fail " + args.key + " missing image"; }
        return {
            priority: 8,
            outlineImage: false,
            render: constructionRender,
            wobble: 0,
            image: args['image'],
            timeDrawn: 0,
            dustImage: "dust",
            scaleTime: 0.15,
            getWobble: getWobble,
            renderState: "default",
            renderTrainIcon: renderTrainIcon,
            outlineTime: 0
        };
    },
    cheerMineButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cheerMineButton)));
        return ecs.memoryResourceButton(args);
    },
    socialMine_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.socialMine_personD)));
        return ecs.personResource(args);
    },
    revertMemory: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            createExecutables: [ecs.revertMemoryExec(args), ecs.killEntity(args)]
        };
    },
    construction: function(args){
        args["touchable"] = true;
        args["canExpandInto"] = false;
        return {
            junctionComponent: ecs.junctionComponent(args),
            rendererComponents: [ecs.constructionRenderComponent(args), ecs.progressRenderComponent(args)],
            personHeldComponent: ecs.personHeldComponent(args),
            managedComponent: ecs.managedComponent(args),
            constructionComponent: ecs.constructionComponent(args),
            createExecutables: [ecs.makeExtraPathsExec(args)].concat([ecs.openMenu(args)]),
            focusPathStopComponent: ecs.constructionFocusPathStopComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            tickComponent: ecs.constructionTickComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    memory_missHome: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_missHome)));
        return ecs.memory(args);
    },
    askOutSuccessMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.askOutSuccessMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    applyAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.applyAction)));
        return ecs.currencyAction(args);
    },
    learnSadMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.learnSadMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    exploreGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.exploreGoal)));
        return ecs.worldGoal(args);
    },
    socialGoalConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.socialGoalConstruction)));
        return ecs.construction(args);
    },
    cleanHouseGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cleanHouseGoal)));
        return ecs.worldGoal(args);
    },
    confessFear: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confessFear)));
        return ecs.emotionSource(args);
    },
    buyEscapeMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.buyEscapeMenu)));
        return ecs.goalCompleteMenu(args);
    },
    killTraitExec: function(args){
        if (args.traitToKill === undefined){ throw "ECS fail " + args.key + " missing traitToKill"; }
        return {
            traitToKill: args['traitToKill'],
            execute: killTraitExec
        };
    },
    worldGoalTimedDeathComponent: function(args){
        return {
            killOnTimeOut: (args["killOnTimeOut"] === undefined) ? [] : args["killOnTimeOut"],
            timeToDeath: args['timeToDeath'],
            deathExecs: [ecs.trackActionables(args)],
            makeOnTimeOut: (args["makeOnTimeOut"] === undefined) ? [] : args["makeOnTimeOut"]
        };
    },
    experienceComponent: function(args){
        return {
            currentLevel: 1,
            updateExpansion: updateExpansion,
            xpPerLevel: 50,
            getXpLevel: getXpLevel,
            gainExp: gainExp,
            maxLevel: (args["maxLevel"] === undefined) ? PARAMETERS.maxExperience : args["maxLevel"]
        };
    },
    fearTickModifyComponent: function(args){
        return {
            tickFactor: args['tickFactor'],
            distractFactor: args['distractFactor'],
            fearFrozen: 0,
            tickModifyInteraction: fearTickModifyInteraction,
            towerId: args['towerId']
        };
    },
    disavowalAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.disavowalAction)));
        return ecs.currencyAction(args);
    },
    memory_dateLucky: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_dateLucky)));
        return ecs.memory(args);
    },
    timedDeathComponent: function(args){
        if (args.timeToDeath === undefined){ throw "ECS fail " + args.key + " missing timeToDeath"; }
        return {
            killOnTimeOut: (args["killOnTimeOut"] === undefined) ? [] : args["killOnTimeOut"],
            timeToDeath: args['timeToDeath'],
            deathExecs: (args["deathExecs"] === undefined) ? [] : args["deathExecs"],
            makeOnTimeOut: (args["makeOnTimeOut"] === undefined) ? [] : args["makeOnTimeOut"]
        };
    },
    focusTickerComponent: function(args){
        return {
            ticks: 2,
            impulseTicks: 10
        };
    },
    emotionTickModifier: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            emotionTickModifyComponent: ecs.emotionTickModifyComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    chessButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chessButton)));
        return ecs.towerButton(args);
    },
    memoryConstructionComponent: function(args){
        if (args.sourceKey === undefined){ throw "ECS fail " + args.key + " missing sourceKey"; }
        return {
            constructionIdentifier: args['constructionIdentifier'],
            killWithoutPath: (args["killWithoutPath"] === undefined) ? false : args["killWithoutPath"],
            ticked: (args["ticked"] === undefined) ? false : args["ticked"],
            construct: memoryConstruct,
            currencyRequested: args['currencyRequested'],
            origCurrencyRequested: _.clone(args.currencyRequested),
            sourceKey: args['sourceKey']
        };
    },
    pointCounterNotificationComponent: function(args){
        return {
            notificationFunctions: simpleDict(PARAMETERS.addPoints, addPoints),
            serializeComponent: ecs.notificationSerializeComponent(args)
        };
    },
    dateSadMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateSadMine)));
        return ecs.emotionResource(args);
    },
    buyRingGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.buyRingGoal)));
        args["particleKey"] = PARAMETERS.newParticle;
        args["description"] = (args.description && args.description[args.personHeld]) ? args.description[args.personHeld] : args.description;
        args["touchable"] = true;
        args["playerKillable"] = !!(args.constructed);
        args["startEntityKey"] = args.personHeld;
        args["canExpandInto"] = false;
        args["currencyRequested"] = derivePersonGoalCurrencies(args.currencyRequested, args.personHeld);
        args["killOnTransform"] = (args["killOnTransform"] === undefined) ? true : args["killOnTransform"];
        return {
            uiActionComponent: ecs.buildingUiActionComponent(args),
            goalTrackComponent: ecs.personGoalTrackComponent(args),
            createExecutables: [ecs.randomOpenPositionExec(args), ecs.buyRingCreateExec(args)],
            timedDeathComponent: (args.timeToDeath) ? ecs.worldGoalTimedDeathComponent(args) : undefined,
            uiDataComponent: ecs.uiDataComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            rendererComponents: [ecs.worldGoalRenderComponent(args), ecs.progressRenderComponent(args), ecs.timedDeathRenderComponent(args)],
            focusTickResponseComponent: (args.aliveEntities) ? ecs.aliveMakeComponent(args) : undefined,
            managedComponent: (args.sourceTrait) ? ecs.managedComponent(args) : undefined,
            flipComponent: (args.flipEntity) ? ecs.flipComponent(args) : undefined,
            worldGoalComponent: ecs.worldGoalComponent(args),
            personHeldComponent: ecs.personHeldComponent(args),
            focusPathStopComponent: ecs.worldGoalFocusPathStopComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            positionComponent: ecs.positionComponent(args),
            notificationComponents: [ecs.worldGoalNotificationComponent(args)],
            junctionComponent: ecs.junctionComponent(args),
            pauseComponent: ecs.pauseComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    memoryResourceFocusTickResponseComponent: function(args){
        return {
            firstTick: true,
            destination: (args["destination"] === undefined) ? undefined : args["destination"],
            focusTick: resourceFocusTick,
            focusTickHook: triggerFlashback,
            flashbackChance: 0.25,
            emotionTags: (args["emotionTags"] === undefined) ? undefined : args["emotionTags"]
        };
    },
    toast: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.toast)));
        args["touchable"] = true;
        return {
            rendererComponents: [ecs.toastRenderComponent(args)],
            timedDeathComponent: ecs.timedDeathComponent(args),
            clickComponent: ecs.toastClickComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            notificationComponents: [ecs.toastNotificationComponent(args)]
        };
    },
    bulletRenderComponent: function(args){
        if (args.bulletSourcePos === undefined){ throw "ECS fail " + args.key + " missing bulletSourcePos"; }
        if (args.bulletEndPos === undefined){ throw "ECS fail " + args.key + " missing bulletEndPos"; }
        if (args.damage === undefined){ throw "ECS fail " + args.key + " missing damage"; }
        if (args.bonus === undefined){ throw "ECS fail " + args.key + " missing bonus"; }
        return {
            render: bulletRender,
            bonus: args['bonus'],
            bulletSourcePos: args['bulletSourcePos'],
            damage: args['damage'],
            priority: 9,
            renderState: "default",
            bulletEndPos: args['bulletEndPos']
        };
    },
    memoryExtension: function(args){
        args["touchable"] = true;
        args["image"] = args.source ? args.source.rendererComponents[0].image : null;
        args["name"] = args.source ? args.source.identityComponent.name : null;
        args["key"] = args.source ? (args.source.identityComponent.key + '_ext') : null;
        args["sourceKey"] = args.source ? args.source.identityComponent.key : null;
        return {
            rendererComponents: [ecs.sourceExtensionRenderComponent(args)],
            positionComponent: ecs.positionComponent(args),
            personHeldComponent: (args.personHeld) ? ecs.personHeldComponent(args) : undefined,
            createExecutables: [ecs.makeExtraPathsExec(args)],
            clickComponent: ecs.sourceExtensionClickComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            sourceExtensionComponent: ecs.sourceExtensionComponent(args),
            playerMadeComponent: ecs.fixedPlayerMadeComponent(args)
        };
    },
    supportGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.supportGoal)));
        return ecs.personGoal(args);
    },
    calmDownGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.calmDownGoal)));
        return ecs.personGoal(args);
    },
    trainVelocityComponent: function(args){
        if (args.trainPath === undefined){ throw "ECS fail " + args.key + " missing trainPath"; }
        if (args.allPaths === undefined){ throw "ECS fail " + args.key + " missing allPaths"; }
        return {
            lastStop: undefined,
            trainStops: undefined,
            stopIndex: 0,
            currentPath: undefined,
            ticks: 0,
            isMoving: false,
            allPaths: args['allPaths'],
            trainPath: args['trainPath'],
            velocity: 1
        };
    },
    trainPathMakerTickComponent: function(args){
        return {
            tick: trainPathMakerTick
        };
    },
    setFlag: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            createExecutables: [ecs.setFlagExec(args), ecs.killEntity(args)]
        };
    },
    steamSpy: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.steamSpy)));
        return ecs.steamAchievement(args);
    },
    bombPrep1Goal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.bombPrep1Goal)));
        return ecs.worldGoal(args);
    },
    flaggedMake: function(args){
        return {
            replaceMarker: ecs.flaggedMakeReplaceMarker(args)
        };
    },
    socialMineButton_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.socialMineButton_personD)));
        return ecs.personResourceButton(args);
    },
    failMakeEntityComponent: function(args){
        args["failEntities"] = (args["failEntities"] === undefined) ? [] : args["failEntities"];
        return {
            execute: executeMakeEntities,
            entitiesToMake: args['failEntities'],
            adjacent: (args["adjacent"] === undefined) ? false : args["adjacent"]
        };
    },
    socialMineButton_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.socialMineButton_personB)));
        return ecs.personResourceButton(args);
    },
    driveTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.driveTowerConstruction)));
        return ecs.construction(args);
    },
    printKillExec: function(args){
        return {
            execute: printKillExec
        };
    },
    worldGoalFocusPathStopComponent: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.worldGoalFocusPathStopComponent)));
        return {
            currencyEntities: (args["currencyEntities"] === undefined) ? undefined : args["currencyEntities"],
            interactWithTrain: currencyRequestedInteractWithTrain,
            currencyEffect: (args["currencyEffect"] === undefined) ? undefined : args["currencyEffect"],
            currenciesNeededAtPoint: currencyRequestedCurrenciesNeededAtPoint,
            updateCurrencyDictWithStop: updateCurrencyDictWithStop,
            getCurrencyComponent: worldGoalGetCurrencyComponent,
            currenciesExistAtPoint: currenciesExistAtPoint,
            currenciesMadeAtPoint: (args["currenciesMadeAtPoint"] === undefined) ? emptyArrayFunction : args["currenciesMadeAtPoint"],
            isPathEnd: true,
            requiredOpinions: (args["requiredOpinions"] === undefined) ? [] : args["requiredOpinions"],
            percentComplete: currencyRequestedFocusPathStopPercentComplete,
            checkFocusStopDisabled: checkFocusStopDisabled,
            isDisabled: false,
            enableChecks: (args["enableChecks"] === undefined) ? [] : args["enableChecks"]
        };
    },
    rechargeButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.rechargeButton)));
        return ecs.memoryTowerButton(args);
    },
    socialMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.socialMenu)));
        return ecs.goalCompleteMenu(args);
    },
    parkDateGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.parkDateGoal)));
        return ecs.personGoal(args);
    },
    steamAchievementNotificationComponent: function(args){
        return {
            notificationFunctions: simpleDict(PARAMETERS.goalComplete, checkSteamAchievement),
            serializeComponent: ecs.notificationSerializeComponent(args)
        };
    },
    chapter4: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chapter4)));
        return ecs.gameStage(args);
    },
    chapter6: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chapter6)));
        return ecs.gameStage(args);
    },
    chapter1: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chapter1)));
        return ecs.gameStage(args);
    },
    workShiftMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.workShiftMenu)));
        return ecs.goalCompleteMenu(args);
    },
    chapter3: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chapter3)));
        return ecs.gameStage(args);
    },
    chapter2: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chapter2)));
        return ecs.gameStage(args);
    },
    gainControlMenuRender: function(args){
        if (args.emotion === undefined){ throw "ECS fail " + args.key + " missing emotion"; }
        if (args.textColor === undefined){ throw "ECS fail " + args.key + " missing textColor"; }
        if (args.text === undefined){ throw "ECS fail " + args.key + " missing text"; }
        if (args.barColor === undefined){ throw "ECS fail " + args.key + " missing barColor"; }
        return {
            emotion: args['emotion'],
            priority: 24,
            fullAnimationTime: 0.25,
            render: gainControlMenuRender,
            brainImage: "miniSelf",
            barWidth: 50,
            percentageComplete: 0,
            barColor: args['barColor'],
            text: args['text'],
            barPos: [250, 150],
            backImage: "uiBack",
            renderTime: 0,
            brainPos: [265, 152],
            textColor: args['textColor'],
            renderState: "default",
            resetVelTime: 0,
            brainVel: 0
        };
    },
    traitMakerComponent: function(args){
        if (args.traitMakerEmotion === undefined){ throw "ECS fail " + args.key + " missing traitMakerEmotion"; }
        if (args.traitsToMake === undefined){ throw "ECS fail " + args.key + " missing traitsToMake"; }
        return {
            traitMakerEmotion: args['traitMakerEmotion'],
            traitsToMake: args['traitsToMake']
        };
    },
    workCompetitionTickComponent: function(args){
        if (args.entitiesToKill === undefined){ throw "ECS fail " + args.key + " missing entitiesToKill"; }
        return {
            entitiesToKill: args['entitiesToKill'],
            tick: workCompetitionTick,
            ticksLeft: 120
        };
    },
    disableMemoriesComponent: function(args){
        return {
            disableNewMemories: true,
            checkTraitDisabled: checkMemoriesDisabled
        };
    },
    trainPathMakerComponent: function(args){
        return {
            addTrainStop: addTrainStop,
            removeTrainStop: removeTrainStop,
            stops: [],
            checkTrainStop: checkTrainStop,
            memoizedCurrenciesNeeded: undefined,
            trainPathKey: PARAMETERS.trainPath,
            makeTrainPath: makeTrainPath,
            getPathCurrenciesNeeded: getPathCurrenciesNeeded
        };
    },
    chapter5: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chapter5)));
        return ecs.gameStage(args);
    },
    talkTower_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.talkTower_personB)));
        return ecs.personTower(args);
    },
    spyButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.spyButton)));
        return ecs.towerButton(args);
    },
    talkTower_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.talkTower_personD)));
        return ecs.personTower(args);
    },
    target: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.target)));
        return ecs.opinion(args);
    },
    executeComponent: function(args){
        if (args.executable === undefined){ throw "ECS fail " + args.key + " missing executable"; }
        return {
            executable: args['executable'],
            ticks: 0,
            requiresActive: (args["requiresActive"] === undefined) ? false : args["requiresActive"],
            impulseTicks: (args["impulseTicks"] === undefined) ? 10 : args["impulseTicks"]
        };
    },
    confessTower_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confessTower_personB)));
        return ecs.personTower(args);
    },
    confessTower_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confessTower_personD)));
        return ecs.personTower(args);
    },
    contactButton_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.contactButton_personD)));
        return ecs.personTowerButton(args);
    },
    married: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.married)));
        return ecs.opinion(args);
    },
    workCompetitionComponent: function(args){
        return {
            workCompleted: 0,
            oppWorkCompleted: 0
        };
    },
    socialMine_personBConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.socialMine_personBConstruction)));
        return ecs.construction(args);
    },
    understandMemoryButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.understandMemoryButton)));
        return ecs.memoryGoalButton(args);
    },
    understandTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.understandTowerConstruction)));
        return ecs.construction(args);
    },
    workChoreMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.workChoreMenu)));
        return ecs.goalCompleteMenu(args);
    },
    holdableComponent: function(args){
        return {
            held: false
        };
    },
    absolutePositionComponent: function(args){
        return {
            getAbsPosRect: getAbsPosRect,
            draggable: (args["draggable"] === undefined) ? true : args["draggable"],
            isDragging: false,
            setAbsPosFromRect: setAbsPosFromRect,
            y: (args["y"] === undefined) ? 0 : args["y"],
            x: (args["x"] === undefined) ? 0 : args["x"],
            size: (args["size"] === undefined) ? [0, 0] : args["size"]
        };
    },
    lostControl: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.lostControl)));
        return {
            createExecutables: [ecs.makeGainControlMenuExec(args), ecs.timeForceExec(args)],
            holdControlComponent: ecs.holdControlComponent(args),
            killExecutables: [ecs.lostControlDeathExec(args)],
            healthComponent: ecs.singleHealthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            identityComponent: ecs.identityComponent(args),
            timeForceComponent: ecs.timeForceComponent(args)
        };
    },
    thinkAboutButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.thinkAboutButton)));
        return ecs.towerButton(args);
    },
    uiActionComponent: function(args){
        return {
            getUiActions: getUiActions,
            generateUiActions: (args["generateUiActions"] === undefined) ? undefined : args["generateUiActions"],
            memoizedActions: undefined
        };
    },
    onTraitComponent: function(args){
        return {
            execute: memoryGoalCompleteExec,
            bonusTime: (args["bonusTime"] === undefined) ? 30 : args["bonusTime"],
            flareMemory: (args["flareMemory"] === undefined) ? false : args["flareMemory"],
            killMemory: (args["killMemory"] === undefined) ? false : args["killMemory"]
        };
    },
    flaggedMakeReplaceMarker: function(args){
        if (args.flagToCheck === undefined){ throw "ECS fail " + args.key + " missing flagToCheck"; }
        if (args.entitiesToMake === undefined){ throw "ECS fail " + args.key + " missing entitiesToMake"; }
        return {
            flagToCheck: args['flagToCheck'],
            entitiesToMake: args['entitiesToMake'],
            getReplaceKeys: flaggedMakeGetReplaceKeys
        };
    },
    emotionSourceComponent: function(args){
        return {
            emotions: unrollDict(args.emotionsToAdd)
        };
    },
    worldGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.worldGoal)));
        args["touchable"] = true;
        args["particleKey"] = PARAMETERS.newParticle;
        args["canExpandInto"] = false;
        args["playerKillable"] = !!(args.constructed);
        args["killOnTransform"] = (args["killOnTransform"] === undefined) ? true : args["killOnTransform"];
        return {
            uiActionComponent: ecs.buildingUiActionComponent(args),
            goalTrackComponent: ecs.checkWorldGoalComponent(args),
            createExecutables: [ecs.checkManagedComponentExec(args), (args.randomPosition) ? ecs.randomOpenPositionExec(args) : undefined, ecs.makeExtraPathsExec(args), ecs.chkForPlayerInterrupt(args), ecs.buildingConnectExec(args), (args.timeToDeath) ? ecs.timedDifficultyModifyExec(args) : undefined],
            timedDeathComponent: (args.timeToDeath) ? ecs.worldGoalTimedDeathComponent(args) : undefined,
            uiDataComponent: ecs.uiDataComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            rendererComponents: [ecs.worldGoalRenderComponent(args), ecs.progressRenderComponent(args), ecs.timedDeathRenderComponent(args)],
            focusTickResponseComponent: (args.aliveEntities) ? ecs.aliveMakeComponent(args) : undefined,
            managedComponent: (args.sourceTrait) ? ecs.managedComponent(args) : undefined,
            flipComponent: (args.flipEntity) ? ecs.flipComponent(args) : undefined,
            worldGoalComponent: ecs.worldGoalComponent(args),
            focusPathStopComponent: ecs.worldGoalFocusPathStopComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            positionComponent: ecs.positionComponent(args),
            notificationComponents: [ecs.worldGoalNotificationComponent(args)],
            junctionComponent: ecs.junctionComponent(args),
            pauseComponent: ecs.pauseComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    willMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.willMine)));
        return ecs.resource(args);
    },
    missionMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.missionMenu)));
        return ecs.goalCompleteMenu(args);
    },
    towerButton: function(args){
        args["subPanel"] = (args["subPanel"] === undefined) ? "Towers" : args["subPanel"];
        args["multiTrait"] = (args["multiTrait"] === undefined) ? false : args["multiTrait"];
        return {
            healthComponent: ecs.singleHealthComponent(args),
            uiActionComponent: ecs.buttonUiActionComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            buttonControlComponent: ecs.towerControlComponent(args),
            createExecutables: [ecs.markTraitNew(args)],
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            killExecutables: [ecs.killTowerButtonExec(args)],
            descriptionComponent: ecs.descriptionComponent(args),
            uniqueComponent: (args.timeToDeath === undefined && !args.multiTrait) ? ecs.uniqueComponent(args) : undefined,
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    flirtButton_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flirtButton_personD)));
        return ecs.personTowerButton(args);
    },
    flagBranchReplaceMarker: function(args){
        if (args.flagToCheck === undefined){ throw "ECS fail " + args.key + " missing flagToCheck"; }
        return {
            flagToCheck: args['flagToCheck'],
            flagEntities: (args["flagEntities"] === undefined) ? [] : args["flagEntities"],
            getReplaceKeys: flagBranchGetReplaceKeys,
            notFlagEntities: (args["notFlagEntities"] === undefined) ? [] : args["notFlagEntities"]
        };
    },
    flirtButton_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flirtButton_personB)));
        return ecs.personTowerButton(args);
    },
    addPersonAndOpinionExec: function(args){
        if (args.personKey === undefined){ throw "ECS fail " + args.key + " missing personKey"; }
        if (args.opinionKey === undefined){ throw "ECS fail " + args.key + " missing opinionKey"; }
        return {
            opinionKey: args['opinionKey'],
            execute: addPersonAndOpinionExec,
            personKey: args['personKey']
        };
    },
    randomReplace: function(args){
        return {
            replaceMarker: ecs.randomReplaceMarker(args)
        };
    },
    tutorialTextEnergyPanel: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialTextEnergyPanel)));
        return ecs.menuTutorialText(args);
    },
    particleEffectRenderComponent: function(args){
        if (args.image === undefined){ throw "ECS fail " + args.key + " missing image"; }
        if (args.effectPos === undefined){ throw "ECS fail " + args.key + " missing effectPos"; }
        if (args.effectRadius === undefined){ throw "ECS fail " + args.key + " missing effectRadius"; }
        return {
            priority: 14,
            startParticles: (args["startParticles"] === undefined) ? 20 : args["startParticles"],
            render: particleEffectRender,
            impulseTimeToCheck: (args["impulseTimeToCheck"] === undefined) ? 0.05 : args["impulseTimeToCheck"],
            image: args['image'],
            effectPos: args['effectPos'],
            particles: [],
            effectRadius: args['effectRadius'],
            timeToCheck: 0.05,
            renderState: "default",
            makeParticle: makeParticle
        };
    },
    featureItemActionExec: function(args){
        if (args.execCurrencyKey === undefined){ throw "ECS fail " + args.key + " missing execCurrencyKey"; }
        if (args.execCurrencyChange === undefined){ throw "ECS fail " + args.key + " missing execCurrencyChange"; }
        return {
            execCurrencyChange: args['execCurrencyChange'],
            execute: featureItemActionExec,
            execCurrencyKey: args['execCurrencyKey']
        };
    },
    gainControl_joy: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.gainControl_joy)));
        return ecs.gainControlMenu(args);
    },
    identityComponent: function(args){
        if (args.key === undefined){ throw "ECS fail " + args.key + " missing key"; }
        if (args.uid === undefined){ throw "ECS fail " + args.key + " missing uid"; }
        return {
            uid: args['uid'],
            tags: (args["tags"] === undefined) ? [] : args["tags"],
            sourceId: (args["sourceId"] === undefined) ? undefined : args["sourceId"],
            image: (args["image"] === undefined) ? undefined : args["image"],
            sourceName: (args["sourceName"] === undefined) ? undefined : args["sourceName"],
            key: args['key'],
            touchable: (args["touchable"] === undefined) ? false : args["touchable"],
            name: (args["name"] === undefined) ? undefined : args["name"]
        };
    },
    disavowalButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.disavowalButton)));
        return ecs.towerButton(args);
    },
    project: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.project)));
        args["touchable"] = true;
        args["health"] = (args["health"] === undefined) ? 2000 : args["health"];
        args["panelKey"] = (args["panelKey"] === undefined) ? PARAMETERS.traitPanel : args["panelKey"];
        return {
            uiActionComponent: ecs.buildingUiActionComponent(args),
            experienceComponent: ecs.experienceComponent(args),
            createExecutables: [ecs.builtSourceCreateExec(args), ecs.makeExtraPathsExec(args)].concat([ecs.randomOpenPositionExec(args)]),
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            uiDataComponent: ecs.uiDataComponent(args),
            entityManagerComponent: ecs.entityManagerComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            isaacEntityComponent: ecs.isaacEntityComponent(args),
            rendererComponents: [ecs.builtSourceRendererComponent(args)],
            focusTickResponseComponent: (args.aliveEntities) ? ecs.focusTickAliveMakerComponent(args) : undefined,
            menuCloseComponent: ecs.traitMenuCloseComponent(args),
            personHeldComponent: (args.personHeld) ? ecs.personHeldComponent(args) : undefined,
            clickComponent: ecs.fullMenuClickComponent(args),
            killExecutables: [ecs.killExtensions(args)].concat([ecs.makeEntityComponent(args)]),
            healthComponent: ecs.healthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            positionComponent: ecs.positionComponent(args),
            mapSpawnComponent: ecs.builtSourceMapSpawnComponent(args),
            junctionComponent: ecs.junctionComponent(args),
            playerMadeComponent: ecs.fixedPlayerMadeComponent(args)
        };
    },
    wobbleRendererComponent: function(args){
        return {
            wobble: 0,
            getWobble: getWobble
        };
    },
    currencyOverloadComponent: function(args){
        if (args.resourceKey === undefined){ throw "ECS fail " + args.key + " missing resourceKey"; }
        if (args.currencyKey === undefined){ throw "ECS fail " + args.key + " missing currencyKey"; }
        if (args.overloadEntities === undefined){ throw "ECS fail " + args.key + " missing overloadEntities"; }
        if (args.currencyCount === undefined){ throw "ECS fail " + args.key + " missing currencyCount"; }
        return {
            resourceKey: args['resourceKey'],
            currencyCount: args['currencyCount'],
            currencyKey: args['currencyKey'],
            overloadEntities: args['overloadEntities']
        };
    },
    learnFirstMission: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.learnFirstMission)));
        return ecs.entityReplace(args);
    },
    memory_nastyComment: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_nastyComment)));
        return ecs.memory(args);
    },
    flipComponent: function(args){
        if (args.flipEntity === undefined){ throw "ECS fail " + args.key + " missing flipEntity"; }
        return {
            flipEntity: args['flipEntity']
        };
    },
    steamJob: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.steamJob)));
        return ecs.steamAchievement(args);
    },
    menuTutorialTextComponent: function(args){
        return {
            entityToMark: args['entityToMark'],
            tutorialDone: menuTutorialDone,
            checkTutorialUiKey: checkTutorialUiKey,
            nextEntities: (args["nextEntities"] === undefined) ? undefined : args["nextEntities"]
        };
    },
    personHealAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.personHealAction)));
        args["personHeld"] = args.source.personHeldComponent.personHeld;
        return {
            healthComponent: ecs.healthComponent(args),
            personHeldComponent: ecs.personHeldComponent(args),
            createExecutables: [ecs.personRelationshipExec(args)]
        };
    },
    killLostControlExec: function(args){
        return {
            execute: killLostControlExec
        };
    },
    tutorialFinishGoalTrain: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialFinishGoalTrain)));
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tutorialTextComponent: ecs.tutorialFinishTrainComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            rendererComponents: [ecs.tutorialTextRenderComponent(args)]
        };
    },
    endGoal: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            createExecutables: [ecs.endGoalExec(args), ecs.killEntity(args)]
        };
    },
    supportGoalConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.supportGoalConstruction)));
        return ecs.construction(args);
    },
    killExtensions: function(args){
        return {
            execute: killExtensions
        };
    },
    trainPathCircRenderComponent: function(args){
        return {
            priority: 12,
            renderTime: 0,
            renderState: "default",
            render: trainPathCircRender
        };
    },
    meetPersonWorldGoalTrackComponent: function(args){
        return {
            successExecutables: [ecs.goalCompleteExec(args)].concat([ecs.trackActionables(args), ecs.makeEntityComponent(args), ecs.makeGoalMenuExecutable(args)]).concat([ecs.addRandomPersonToMap(args)]),
            checkCompleted: checkWorldGoalComponent
        };
    },
    chkForPlayerInterrupt: function(args){
        return {
            execute: chkForPlayerInterruptExec,
            playerExecute: ecs.makeParticleEffect(args)
        };
    },
    joy: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.joy)));
        return ecs.creep(args);
    },
    trainPanel: function(args){
        args["x"] = width - 300;
        args["size"] = [300, 400];
        args["size"] = (args["size"] === undefined) ? [550, 450] : args["size"];
        return {
            rendererComponents: [ecs.trainPanelRenderComponent(args)],
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            uiClickComponent: ecs.panelUiClickComponent(args),
            helpPanelComponent: ecs.helpPanelComponent(args),
            panelComponent: ecs.entityPanelComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    entityPanel: function(args){
        args["size"] = (args["size"] === undefined) ? [550, 450] : args["size"];
        return {
            rendererComponents: [ecs.entityPanelRenderComponent(args)],
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            uiClickComponent: ecs.panelUiClickComponent(args),
            helpPanelComponent: ecs.helpPanelComponent(args),
            panelComponent: ecs.entityPanelComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    activationGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.activationGoal)));
        return ecs.worldGoal(args);
    },
    contactTower_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.contactTower_personB)));
        return ecs.personTower(args);
    },
    cautionMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cautionMineConstruction)));
        return ecs.memoryConstruction(args);
    },
    disablePersonD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.disablePersonD)));
        return ecs.disableTrait(args);
    },
    towerFlashRenderComponent: function(args){
        return {
            priority: 6,
            images: ["border0", "border1", "border2"],
            renderState: "default",
            timeToDeath: 0.5,
            render: towerFlashRender
        };
    },
    paintTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.paintTowerConstruction)));
        return ecs.construction(args);
    },
    contactTower_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.contactTower_personD)));
        return ecs.personTower(args);
    },
    personGoalTrackComponent: function(args){
        return {
            successExecutables: [ecs.goalCompleteExec(args)].concat([ecs.trackActionables(args), ecs.makeEntityComponent(args), ecs.makeGoalMenuExecutable(args)]).concat([ecs.addOpinionExec(args)]),
            checkCompleted: checkWorldGoalComponent
        };
    },
    currencyBoostComponent: function(args){
        return {
            currencyBoost: (args["currencyBoost"] === undefined) ? {} : args["currencyBoost"]
        };
    },
    focusPanelRenderComponent: function(args){
        return {
            insetColor: "#000000",
            showFocusPanel: undefined,
            fullAnimationTime: 0.1,
            yTextOffset: 5,
            backImage: "opaqueBack",
            drawImageButton: drawImageButton,
            animatingTime: 0.1,
            priority: 19,
            render: focusPanelRender,
            drawPanelButton: drawPanelButton,
            snapPanelPos: snapPanelPos,
            toggleFocusPath: undefined,
            renderTime: 0,
            textColor: "#000000",
            renderState: "default",
            specificRender: args['specificRender'],
            drawHelpButton: drawHelpButton
        };
    },
    memory_disavowAnger: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_disavowAnger)));
        return ecs.memory(args);
    },
    acceptFearMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.acceptFearMine)));
        return ecs.emotionResource(args);
    },
    openMenuMarker: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            rendererComponents: [ecs.openMenuMarkerRenderComponent(args)],
            openMenuMarkerComponent: ecs.openMenuMarkerComponent(args)
        };
    },
    findCleanJobGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.findCleanJobGoal)));
        return ecs.worldGoal(args);
    },
    bombPrep3Goal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.bombPrep3Goal)));
        return ecs.worldGoal(args);
    },
    learnSadMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.learnSadMine)));
        return ecs.emotionResource(args);
    },
    tutorialWrongButtonRenderComponent: function(args){
        return {
            priority: 24,
            text: "First close this",
            renderState: "default",
            render: tutorialWrongButtonRender
        };
    },
    autosaver: function(args){
        return {
            tickComponent: ecs.autosaveTickComponent(args)
        };
    },
    traitPanel: function(args){
        args["size"] = [740, 500];
        args["size"] = (args["size"] === undefined) ? [550, 450] : args["size"];
        return {
            rendererComponents: [ecs.traitPanelRenderComponent(args)],
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            uiClickComponent: ecs.panelUiClickComponent(args),
            helpPanelComponent: ecs.helpPanelComponent(args),
            panelComponent: ecs.entityPanelComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    job: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.job)));
        return ecs.dailyEntityMaker(args);
    },
    restaurantDateMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.restaurantDateMenu)));
        return ecs.goalCompleteMenu(args);
    },
    flashback_anger: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flashback_anger)));
        return ecs.emotionResource(args);
    },
    dateLucky: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateLucky)));
        return ecs.emotionSource(args);
    },
    tutorialPreDefense: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialPreDefense)));
        return ecs.menuTutorialText(args);
    },
    gainControl_sadness: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.gainControl_sadness)));
        return ecs.gainControlMenu(args);
    },
    settingsPanelRenderComponent: function(args){
        return {
            sfxVolFunctions: undefined,
            fullAnimationTime: 0.1,
            yTextOffset: 5,
            drawPanelButton: drawPanelButton,
            quitFunction: undefined,
            renderState: "default",
            insetColor: "#000000",
            difficultyFunctions: undefined,
            priority: 18,
            snapPanelPos: snapPanelPos,
            soundVolFunctions: undefined,
            textColor: "#000000",
            backImage: "uiBack",
            drawImageButton: drawImageButton,
            render: outPanelRender,
            specificRender: settingsPanelRender,
            soundFunction: undefined,
            renderTime: 0,
            drawHelpButton: drawHelpButton,
            animatingTime: 0.1,
            fullscreenFunction: undefined,
            sfxFunction: undefined
        };
    },
    cleanChoreMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cleanChoreMenu)));
        return ecs.goalCompleteMenu(args);
    },
    flashback_sadnessConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flashback_sadnessConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    disableTrait: function(args){
        return {
            disableTraitComponent: ecs.disableTraitComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined
        };
    },
    tutorialWrongButton: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tutorialTextComponent: ecs.tutorialWrongButtonComponent(args),
            rendererComponents: [ecs.tutorialWrongButtonRenderComponent(args)]
        };
    },
    controlPanelKeyPressComponent: function(args){
        return {
            handleKeyPress: controlPanelHandleKeyPress
        };
    },
    memory_programming: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_programming)));
        return ecs.memory(args);
    },
    deposit: function(args){
        args["touchable"] = true;
        args["canExpandInto"] = false;
        return {
            junctionComponent: ecs.junctionComponent(args),
            rendererComponents: [ecs.pathStopRenderComponent(args), ecs.timedDeathRenderComponent(args)],
            uiActionComponent: ecs.buildingUiActionComponent(args),
            focusTickResponseComponent: ecs.aliveMakeComponent(args),
            depositComponent: ecs.depositComponent(args),
            managedComponent: (args.sourceTrait) ? ecs.managedComponent(args) : undefined,
            createExecutables: [ecs.checkMakeDeposit(args), ecs.depositPositionExec(args), ecs.makeExtraPathsExec(args), ecs.buildingConnectExec(args), (args.timeToDeath) ? ecs.timedDifficultyModifyExec(args) : undefined],
            focusPathStopComponent: ecs.depositFocusPathStopComponent(args),
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            clickComponent: ecs.fullMenuClickComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            tickComponent: ecs.depositTickComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    askAboutGoalConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.askAboutGoalConstruction)));
        return ecs.construction(args);
    },
    escapeGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.escapeGoal)));
        return ecs.worldGoal(args);
    },
    cabChoreMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cabChoreMenu)));
        return ecs.goalCompleteMenu(args);
    },
    tutorialTextEnergyPanel2: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialTextEnergyPanel2)));
        return ecs.menuTutorialText(args);
    },
    instantEmotionResource: function(args){
        args["touchable"] = true;
        args["canExpandInto"] = false;
        args["playerKillable"] = false;
        return {
            focusPathStopComponent: ecs.resourceFocusPathStopComponent(args),
            rendererComponents: [ecs.pathStopRenderComponent(args)],
            focusTickResponseComponent: ecs.resourceFocusTickResponseComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            managedComponent: ecs.managedComponent(args),
            createExecutables: [ecs.makeExtraPathsExec(args)].concat([ecs.emotionResourceDifficultyExec(args)]),
            junctionComponent: ecs.junctionComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            killExecutables: [ecs.killResourceCurrencies(args)],
            resourceComponent: ecs.resourceComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            loseControlExec: ecs.killEntity(args),
            playerMadeComponent: ecs.playerMadeComponent(args),
            tickComponent: ecs.instantEmotionResourceTickComponent(args)
        };
    },
    socialGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.socialGoal)));
        return ecs.personGoal(args);
    },
    askOutSuccessMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.askOutSuccessMine)));
        return ecs.emotionResource(args);
    },
    path: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.path)));
        args["y"] = args['start'][1];
        args["x"] = args['start'][0];
        args["name"] = 'path(' + args.x + ',' + args.y + ')';;
        args["key"] = 'path_' + args.x + '_' + args.y + '_' + args.end[0] + '_' + args.end[1];;
        return {
            rendererComponents: [ecs.pathConnectRenderComponent(args)],
            pathMoveComponent: ecs.pathMoveComponent(args),
            createExecutables: [ecs.duplicatePathExec(args)],
            healthComponent: ecs.hundredHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    contactTower_personBConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.contactTower_personBConstruction)));
        return ecs.construction(args);
    },
    goalCompleteMenuRender: function(args){
        return {
            flipModeFunction: undefined,
            fullAnimationTime: 7,
            yTextOffset: 5,
            closeFunction: undefined,
            image: (args["image"] === undefined) ? "activeHex" : args["image"],
            height: (args["height"] === undefined) ? undefined : args["height"],
            drawPanelButton: drawPanelButton,
            animatingTime: 0.2,
            descriptionTime: 0.5,
            renderState: "default",
            insetColor: "#000000",
            render: outPanelRender,
            titleTime: 0.4,
            priority: 19,
            sourceTrait: (args["sourceTrait"] === undefined) ? undefined : args["sourceTrait"],
            snapPanelPos: snapPanelPos,
            carousel: undefined,
            textColor: "#000000",
            backImage: "uiBack",
            drawImageButton: drawImageButton,
            expandRect: [args.x, args.y, args.size[0], args.size[1]],
            renderTime: 0,
            getFlipModeFunction: goalCompleteMenuFlipMode,
            completeText: (args["completeText"] === undefined) ? "" : args["completeText"],
            drawHelpButton: drawHelpButton,
            showingDescription: true,
            expandFunction: undefined,
            endGame: (args["endGame"] === undefined) ? false : args["endGame"],
            showPoints: (args["showPoints"] === undefined) ? false : args["showPoints"],
            specificRender: goalCompleteMenuRender
        };
    },
    gameStage: function(args){
        return {
            createExecutables: [ecs.makeOnCreateEntityExec(args)],
            stageChangeComponent: ecs.stageChangeComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            dayResponseComponent: ecs.gameStageDayResponseComponent(args),
            notificationComponents: [ecs.gameStageNotificationComponent(args)]
        };
    },
    nameRenderComponent: function(args){
        return {
            priority: 8,
            renderState: "default",
            name: args['name'],
            render: nameRender
        };
    },
    choreTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.choreTowerConstruction)));
        return ecs.construction(args);
    },
    transformExec: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            createExecutables: [ecs.transformTrait(args), ecs.killEntity(args)]
        };
    },
    depositComponent: function(args){
        if (args.deposit === undefined){ throw "ECS fail " + args.key + " missing deposit"; }
        return {
            deposit: args['deposit']
        };
    },
    learnAboutGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.learnAboutGoal)));
        return ecs.personGoal(args);
    },
    dateReplace: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateReplace)));
        return ecs.randomReplace(args);
    },
    disavowSadnessMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.disavowSadnessMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    justTalkMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.justTalkMenu)));
        return ecs.goalCompleteMenu(args);
    },
    programmingMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.programmingMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    makeBuyRingGoalCreate: function(args){
        return {
            execute: makeBuyRingGoalCreate
        };
    },
    contactButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.contactButton)));
        return ecs.towerButton(args);
    },
    randomMenuMarker: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            randomMenuComponent: ecs.randomMenuComponent(args)
        };
    },
    memory_threat: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_threat)));
        return ecs.memory(args);
    },
    noFocusNotifier: function(args){
        return {
            rendererComponents: [ecs.noFocusNotifierRenderComponent(args)]
        };
    },
    towerTickComponent: function(args){
        return {
            tick: towerTick,
            timeToAttrition: (args["timeToAttrition"] === undefined) ? PARAMETERS.dayTicks : args["timeToAttrition"]
        };
    },
    wishMakingComponent: function(args){
        if (args.makeWish === undefined){ throw "ECS fail " + args.key + " missing makeWish"; }
        if (args.canMakeWish === undefined){ throw "ECS fail " + args.key + " missing canMakeWish"; }
        return {
            makeWish: args['makeWish'],
            canMakeWish: args['canMakeWish']
        };
    },
    missHomeMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.missHomeMine)));
        return ecs.emotionResource(args);
    },
    selfExtensionTickComponent: function(args){
        return {
            tick: selfExtensionTick
        };
    },
    resourceModifierComponent: function(args){
        if (args.stopResources === undefined){ throw "ECS fail " + args.key + " missing stopResources"; }
        if (args.doubleResources === undefined){ throw "ECS fail " + args.key + " missing doubleResources"; }
        if (args.towerId === undefined){ throw "ECS fail " + args.key + " missing towerId"; }
        return {
            doubleResources: args['doubleResources'],
            stopResources: args['stopResources'],
            towerId: args['towerId']
        };
    },
    city_ext: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.city_ext)));
        return ecs.sourceExtension(args);
    },
    worldGoalComponent: function(args){
        if (args.currencyRequested === undefined){ throw "ECS fail " + args.key + " missing currencyRequested"; }
        return {
            goalSeen: false,
            requestingCurrency: false,
            currencyRequested: args['currencyRequested'],
            origCurrencyRequested: _.clone(args.currencyRequested)
        };
    },
    tutorialFinishTrain: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialFinishTrain)));
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tutorialTextComponent: ecs.tutorialFinishTrainComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            rendererComponents: [ecs.tutorialTextRenderComponent(args)]
        };
    },
    entityKill: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            createExecutables: [ecs.killEntityComponent(args), ecs.killEntity(args)]
        };
    },
    requestComponent: function(args){
        if (args.currencyRequested === undefined){ throw "ECS fail " + args.key + " missing currencyRequested"; }
        return {
            currencyRequested: args['currencyRequested']
        };
    },
    singleHealthComponent: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.singleHealthComponent)));
        return {
            kill: killHealthComponent,
            damageFactor: (args["damageFactor"] === undefined) ? 1 : args["damageFactor"],
            health: args['health'],
            maxHealth: args['health']
        };
    },
    healthComponent: function(args){
        if (args.health === undefined){ throw "ECS fail " + args.key + " missing health"; }
        return {
            kill: killHealthComponent,
            damageFactor: (args["damageFactor"] === undefined) ? 1 : args["damageFactor"],
            health: args['health'],
            maxHealth: args['health']
        };
    },
    wakeGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.wakeGoal)));
        return ecs.worldGoal(args);
    },
    sourceExtensionComponent: function(args){
        if (args.sourceKey === undefined){ throw "ECS fail " + args.key + " missing sourceKey"; }
        return {
            sourceKey: args['sourceKey']
        };
    },
    fightProgramGoalConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fightProgramGoalConstruction)));
        return ecs.construction(args);
    },
    creepDeathRenderComponent: function(args){
        if (args.image === undefined){ throw "ECS fail " + args.key + " missing image"; }
        return {
            timeLeft: 1,
            render: creepDeathRender,
            renderPos: (args["renderPos"] === undefined) ? undefined : args["renderPos"],
            image: args['image'],
            priority: 12,
            renderState: "default",
            size: [20, 17]
        };
    },
    blinkTrainStops: function(args){
        return {
            execute: blinkTrainStopsExec,
            image: "activeOutline-0099ff"
        };
    },
    thinkAboutTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.thinkAboutTowerConstruction)));
        return ecs.construction(args);
    },
    memory_askOutSuccess: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_askOutSuccess)));
        return ecs.memory(args);
    },
    worried: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.worried)));
        return ecs.opinion(args);
    },
    acceptFear: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.acceptFear)));
        return ecs.emotionSource(args);
    },
    cash: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cash)));
        return ecs.currency(args);
    },
    choreAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.choreAction)));
        return ecs.currencyAction(args);
    },
    tower: function(args){
        args["touchable"] = true;
        args["canExpandInto"] = false;
        args["isPathEnd"] = (args["isPathEnd"] === undefined) ? (args.damageMultipliers ? true : false) : args["isPathEnd"];
        args["tags"] = (args["tags"] === undefined) ? [args.key] : args["tags"];
        return {
            junctionComponent: ecs.junctionComponent(args),
            rendererComponents: [ecs.pathStopRenderComponent(args), ecs.damageRenderComponent(args)],
            focusTickResponseComponent: ecs.towerFocusTickResponseComponent(args),
            managedComponent: ecs.managedComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            damageComponent: (args.damageMultipliers) ? ecs.damageComponent(args) : undefined,
            healthModifyComponent: ecs.healthModifyComponent(args),
            createExecutables: [ecs.makeExtraPathsExec(args)].concat([ecs.towerCreateExec(args)]),
            focusPathStopComponent: ecs.towerFocusPathStopComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            tickComponent: ecs.towerTickComponent(args),
            towerComponent: ecs.towerComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    will: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.will)));
        return ecs.currency(args);
    },
    nullReplace: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.nullReplace)));
        return ecs.entityReplace(args);
    },
    pauseComponent: function(args){
        return {
            paused: true
        };
    },
    dateSadMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateSadMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    opinionTickComponent: function(args){
        return {
            emotionsToMake: (args["emotionsToMake"] === undefined) ? undefined : args["emotionsToMake"],
            tick: opinionTick,
            impulseTicks: (args["impulseTicks"] === undefined) ? 30 : args["impulseTicks"],
            ticks: 0
        };
    },
    selfControlGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.selfControlGoal)));
        return ecs.personGoal(args);
    },
    energyMineButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.energyMineButton)));
        return ecs.resourceButton(args);
    },
    unpackMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.unpackMenu)));
        return ecs.goalCompleteMenu(args);
    },
    flashback_fear: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flashback_fear)));
        return ecs.emotionResource(args);
    },
    starParticleEffectRenderComponent: function(args){
        if (args.effectPos === undefined){ throw "ECS fail " + args.key + " missing effectPos"; }
        return {
            priority: 14,
            render: starParticleEffectRender,
            image: "star",
            effectPos: args['effectPos'],
            timeDrawn: 0,
            scaleTime: 0.15,
            renderState: "default"
        };
    },
    motherlandPathAdult: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.motherlandPathAdult)));
        return ecs.pathMaker(args);
    },
    planEscapeGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.planEscapeGoal)));
        return ecs.worldGoal(args);
    },
    builtSourceCreateExec: function(args){
        return {
            sector: (args["sector"] === undefined) ? undefined : args["sector"],
            skipBuiltSourceCreate: (args["skipBuiltSourceCreate"] === undefined) ? false : args["skipBuiltSourceCreate"],
            execute: builtSourceCreateExec,
            startManagedEntities: (args["startManagedEntities"] === undefined) ? [] : args["startManagedEntities"],
            randomTraitPosition: (args["randomTraitPosition"] === undefined) ? true : args["randomTraitPosition"]
        };
    },
    workCompetitionNotificationComponent: function(args){
        if (args.goalKey === undefined){ throw "ECS fail " + args.key + " missing goalKey"; }
        return {
            notificationFunctions: simpleDict(PARAMETERS.goalComplete, workCompetitionNotify),
            serializeComponent: ecs.notificationSerializeComponent(args),
            goalKey: args['goalKey']
        };
    },
    duplicatePathExec: function(args){
        return {
            execute: duplicatePathExec
        };
    },
    dateFailMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateFailMine)));
        return ecs.emotionResource(args);
    },
    shame: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.shame)));
        return ecs.emotionSource(args);
    },
    defineSelfMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.defineSelfMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    talkButton_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.talkButton_personB)));
        return ecs.personTowerButton(args);
    },
    trainPathMakerKill: function(args){
        return {
            execute: trainPathMakerKill
        };
    },
    justTalkGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.justTalkGoal)));
        return ecs.personGoal(args);
    },
    findJob: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.findJob)));
        return ecs.entityReplace(args);
    },
    social_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.social_personD)));
        return ecs.personCurrency(args);
    },
    timedDeathRenderComponent: function(args){
        return {
            priority: 7,
            renderState: "default",
            render: timedDeathRender
        };
    },
    notifyEntityMaker: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            notificationComponents: [ecs.notifyEntityMakerNotificationComponent(args)]
        };
    },
    paramPath: function(args){
        return {
            createExecutables: [ecs.paramPathExec(args), ecs.killEntity(args)]
        };
    },
    tutorialPlaceEnergy: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialPlaceEnergy)));
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tutorialTextComponent: ecs.tutorialPlaceEnergyComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            rendererComponents: [ecs.tutorialTextRenderComponent(args)]
        };
    },
    timedScaleRenderComponent: function(args){
        if (args.image === undefined){ throw "ECS fail " + args.key + " missing image"; }
        return {
            timeDrawn: 0,
            render: timedRender,
            image: args['image'],
            nextRenderComponent: args['nextRenderComponent'],
            renderWhileTimed: args['renderWhileTimed'],
            renderTime: (args["renderTime"] === undefined) ? 0.15 : args["renderTime"],
            renderWhenTimed: timedScaleRenderWhenTimed,
            renderState: "default",
            priority: args['priority']
        };
    },
    experienceTriggerComponent: function(args){
        if (args.traitKey === undefined){ throw "ECS fail " + args.key + " missing traitKey"; }
        if (args.xpEntities === undefined){ throw "ECS fail " + args.key + " missing xpEntities"; }
        return {
            traitKey: args['traitKey'],
            xpEntities: args['xpEntities']
        };
    },
    planEscapeMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.planEscapeMenu)));
        return ecs.goalCompleteMenu(args);
    },
    confessionMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confessionMenu)));
        return ecs.goalCompleteMenu(args);
    },
    cleanTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cleanTowerConstruction)));
        return ecs.construction(args);
    },
    traitPanelRenderComponent: function(args){
        return {
            insetColor: "#000000",
            fullAnimationTime: 0.1,
            yTextOffset: 5,
            backImage: "uiBack",
            drawImageButton: drawImageButton,
            animatingTime: 0.1,
            priority: 18,
            render: traitPanelRender,
            drawPanelButton: drawPanelButton,
            snapPanelPos: snapPanelPos,
            currentCategory: undefined,
            setCategoryFn: {},
            renderTime: 0,
            baseSize: args['size'],
            textColor: "#000000",
            renderState: "default",
            specificRender: args['specificRender'],
            drawHelpButton: drawHelpButton
        };
    },
    confessButton_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confessButton_personB)));
        return ecs.personTowerButton(args);
    },
    walkAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.walkAction)));
        return ecs.currencyAction(args);
    },
    confessButton_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confessButton_personD)));
        return ecs.personTowerButton(args);
    },
    killIfFlagExec: function(args){
        return {
            killIfFlag: (args["killIfFlag"] === undefined) ? undefined : args["killIfFlag"],
            execute: killIfFlagExec
        };
    },
    wakeMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.wakeMenu)));
        return ecs.goalCompleteMenu(args);
    },
    readHomeGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.readHomeGoal)));
        return ecs.worldGoal(args);
    },
    builtSourceRendererComponent: function(args){
        if (args.image === undefined){ throw "ECS fail " + args.key + " missing image"; }
        return {
            outlineImage: "activeOutline",
            render: builtSourceRender,
            wobble: 0,
            image: args['image'],
            priority: 5,
            timeToBlink: 1,
            newImage: "newOutline",
            getWobble: getWobble,
            renderState: "default",
            disabledImage: "trait-aaaaaa"
        };
    },
    spyGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.spyGoal)));
        return ecs.worldGoal(args);
    },
    cleanCompete: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cleanCompete)));
        return ecs.workCompetition(args);
    },
    askOutGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.askOutGoal)));
        return ecs.personGoal(args);
    },
    tutorialTextComponent: function(args){
        if (args.entityToMark === undefined){ throw "ECS fail " + args.key + " missing entityToMark"; }
        return {
            entityToMark: args['entityToMark'],
            tutorialDone: tutorialTextTutorialDone,
            checkTutorialUiKey: checkTutorialUiKey,
            nextEntities: (args["nextEntities"] === undefined) ? undefined : args["nextEntities"]
        };
    },
    panelTrackComponent: function(args){
        return {
            closeTrackedPanel: closeTrackedPanel,
            panelsOpened: [],
            closeAllPanels: closeAllPanels,
            trackPanels: trackPanels
        };
    },
    frustrationMineButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.frustrationMineButton)));
        return ecs.memoryResourceButton(args);
    },
    trainPathSecondRender: function(args){
        return {
            priority: 3,
            image: "outline",
            renderState: "default",
            render: trainPathSecondRender
        };
    },
    cautionMineButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cautionMineButton)));
        return ecs.memoryResourceButton(args);
    },
    resourceModifier: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.resourceModifier)));
        return {
            healthComponent: ecs.singleHealthComponent(args),
            resourceModifierComponent: ecs.resourceModifierComponent(args),
            rendererComponents: [ecs.resourceModifierRenderComponent(args)],
            dayResponseComponent: ecs.killDayEndComponent(args),
            timedDeathComponent: ecs.timedDeathComponent(args)
        };
    },
    allTrainPanelRenderComponent: function(args){
        return {
            insetColor: "#000000",
            fullAnimationTime: 0.1,
            yTextOffset: 5,
            backImage: "uiBack",
            drawImageButton: drawImageButton,
            animatingTime: 0.1,
            priority: 18,
            render: outPanelRender,
            drawPanelButton: drawPanelButton,
            snapPanelPos: snapPanelPos,
            renderTime: 0,
            textColor: "#000000",
            renderState: "default",
            specificRender: allTrainPanelRender,
            drawHelpButton: drawHelpButton
        };
    },
    trainRenderComponent: function(args){
        if (args.trainColor === undefined){ throw "ECS fail " + args.key + " missing trainColor"; }
        return {
            priority: 11,
            render: trainRender,
            linePos: false,
            timeDrawn: 0,
            previousPositions: [],
            renderState: "default",
            trainColor: args['trainColor']
        };
    },
    serializeComponent: function(args){
        if (args.serialize === undefined){ throw "ECS fail " + args.key + " missing serialize"; }
        if (args.postSerialize === undefined){ throw "ECS fail " + args.key + " missing postSerialize"; }
        return {
            serialize: args['serialize'],
            postSerialize: args['postSerialize']
        };
    },
    gainControlMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.gainControlMenu)));
        args["size"] = [550, 350];
        return {
            healthComponent: ecs.singleHealthComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            rendererComponents: [ecs.gainControlMenuRender(args)],
            killExecutables: [ecs.killLostControlExec(args)]
        };
    },
    killWork: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.killWork)));
        return ecs.entityKill(args);
    },
    desireMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.desireMine)));
        return ecs.resource(args);
    },
    creepComponent: function(args){
        if (args.emotion === undefined){ throw "ECS fail " + args.key + " missing emotion"; }
        return {
            emotion: args['emotion'],
            towerModifier: (args["towerModifier"] === undefined) ? args['undefined'] : args["towerModifier"],
            selfDamage: PARAMETERS.creepSelfDamage
        };
    },
    worldGoalNotificationComponent: function(args){
        return {
            notificationFunctions: simpleDict(PARAMETERS.openMenu, worldGoalMenuOpened),
            serializeComponent: ecs.notificationSerializeComponent(args)
        };
    },
    talkTower_personBConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.talkTower_personBConstruction)));
        return ecs.construction(args);
    },
    fullMenuClickComponent: function(args){
        if (args.image === undefined){ throw "ECS fail " + args.key + " missing image"; }
        return {
            image: args['image'],
            onClick: fullMenuOnClick,
            uiKey: (args["uiKey"] === undefined) ? getUiKey(args.key, args.uid) : args["uiKey"],
            panelKey: (args["panelKey"] === undefined) ? PARAMETERS.towerPanel : args["panelKey"]
        };
    },
    personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.personB)));
        return ecs.person(args);
    },
    tutorialUnpauseRenderComponent: function(args){
        if (args.text === undefined){ throw "ECS fail " + args.key + " missing text"; }
        if (args.yStart === undefined){ throw "ECS fail " + args.key + " missing yStart"; }
        return {
            priority: 24,
            text: args['text'],
            renderState: "default",
            yStart: args['yStart'],
            render: tutorialUnpauseRender
        };
    },
    personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.personD)));
        return ecs.person(args);
    },
    workShiftButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.workShiftButton)));
        return ecs.goalButton(args);
    },
    findCleanJobMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.findCleanJobMenu)));
        return ecs.goalCompleteMenu(args);
    },
    dateTower_personDConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateTower_personDConstruction)));
        return ecs.construction(args);
    },
    papersGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.papersGoal)));
        return ecs.worldGoal(args);
    },
    worldGoalParticleEffect: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            lifeSentinelComponent: ecs.worldGoalParticleEffectLifeSentinelComponent(args),
            rendererComponents: [ecs.particleEffectRenderComponent(args)]
        };
    },
    thought: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.thought)));
        return ecs.currency(args);
    },
    person: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.person)));
        args["touchable"] = true;
        args["health"] = (args["health"] === undefined) ? 2000 : args["health"];
        args["panelKey"] = (args["panelKey"] === undefined) ? PARAMETERS.traitPanel : args["panelKey"];
        return {
            uiActionComponent: ecs.buildingUiActionComponent(args),
            experienceComponent: ecs.experienceComponent(args),
            createExecutables: [ecs.builtSourceCreateExec(args), ecs.makeExtraPathsExec(args)].concat([ecs.personCreateExec(args)]),
            uiDataComponent: ecs.uiDataComponent(args),
            entityManagerComponent: ecs.entityManagerComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            isaacEntityComponent: ecs.isaacEntityComponent(args),
            rendererComponents: [ecs.builtSourceRendererComponent(args)],
            focusTickResponseComponent: (args.aliveEntities) ? ecs.focusTickAliveMakerComponent(args) : undefined,
            menuCloseComponent: ecs.traitMenuCloseComponent(args),
            personHeldComponent: (args.personHeld) ? ecs.personHeldComponent(args) : undefined,
            clickComponent: ecs.fullMenuClickComponent(args),
            killExecutables: [ecs.killExtensions(args)].concat([ecs.personKillExec(args)]),
            healthComponent: ecs.healthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            positionComponent: ecs.positionComponent(args),
            personalityComponent: ecs.personalityComponent(args),
            mapSpawnComponent: ecs.builtSourceMapSpawnComponent(args),
            junctionComponent: ecs.junctionComponent(args),
            playerMadeComponent: ecs.fixedPlayerMadeComponent(args)
        };
    },
    cheer: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cheer)));
        return ecs.currency(args);
    },
    contact: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.contact)));
        return ecs.currency(args);
    },
    checkManagedComponentExec: function(args){
        return {
            execute: checkManagedComponent
        };
    },
    tutorialTextAdulthood: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialTextAdulthood)));
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tutorialTextComponent: ecs.tutorialTextAdulthoodComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            rendererComponents: [ecs.tutorialTextRenderComponent(args)]
        };
    },
    emotionResourceMapComponent: function(args){
        if (args.emotionToMap === undefined){ throw "ECS fail " + args.key + " missing emotionToMap"; }
        if (args.emotionMappedEntities === undefined){ throw "ECS fail " + args.key + " missing emotionMappedEntities"; }
        return {
            emotionMappedEntities: args['emotionMappedEntities'],
            startManagedEntities: (args["startManagedEntities"] === undefined) ? [] : args["startManagedEntities"],
            emotionToMap: args['emotionToMap']
        };
    },
    meetPersonGoalConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.meetPersonGoalConstruction)));
        return ecs.construction(args);
    },
    personResourceButton: function(args){
        args["subPanel"] = (args["subPanel"] === undefined) ? "Towers" : args["subPanel"];
        args["multiTrait"] = (args["multiTrait"] === undefined) ? false : args["multiTrait"];
        return {
            interactionComponent: ecs.interactionComponent(args),
            uiActionComponent: ecs.buttonUiActionComponent(args),
            buttonControlComponent: ecs.towerControlComponent(args),
            personHeldComponent: ecs.personHeldComponent(args),
            createExecutables: [ecs.markTraitNew(args)],
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            uiDataComponent: ecs.uiDataComponent(args),
            uniqueComponent: (args.timeToDeath === undefined && !args.multiTrait) ? ecs.uniqueComponent(args) : undefined,
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            killExecutables: [ecs.killTowerButtonExec(args)],
            identityComponent: ecs.identityComponent(args)
        };
    },
    chore: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chore)));
        return ecs.currency(args);
    },
    opinionComponent: function(args){
        return {
            successOpinions: (args["successOpinions"] === undefined) ? {} : args["successOpinions"]
        };
    },
    damageIndicator: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.damageIndicator)));
        return {
            healthComponent: ecs.singleHealthComponent(args),
            timedDeathComponent: ecs.timedDeathComponent(args),
            rendererComponents: [ecs.damageIndicatorRenderComponent(args)]
        };
    },
    dateTower_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateTower_personD)));
        return ecs.personTower(args);
    },
    buyRingButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.buyRingButton)));
        return ecs.personGoalButton(args);
    },
    dateTower_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateTower_personB)));
        return ecs.personTower(args);
    },
    cleanChoreStartCheck: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cleanChoreStartCheck)));
        return ecs.notifyEntityMaker(args);
    },
    goalCompleteMenuComponent: function(args){
        return {
            path: []
        };
    },
    cabCompeteCheck: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cabCompeteCheck)));
        return ecs.notifyEntityMaker(args);
    },
    builtSourceMapSpawnComponent: function(args){
        args["mapSpawnKey"] = (args["mapSpawnKey"] === undefined) ? args.key + '_ext' : args["mapSpawnKey"];
        return {
            spawnDone: false,
            mapPostSpawn: trueFunction,
            tickos: 15,
            mapSpawnPos: builtSourceMapSpawnPos,
            mapShouldSpawn: builtSourceMapShouldSpawn,
            impulseTicks: 15,
            mapSpawn: mapSpawn,
            tendPosition: (args["tendPosition"] === undefined) ? false : args["tendPosition"],
            mapSpawnKey: args['mapSpawnKey']
        };
    },
    disableTraitComponent: function(args){
        if (args.traitsToDisable === undefined){ throw "ECS fail " + args.key + " missing traitsToDisable"; }
        return {
            checkTraitDisabled: checkTraitDisabled,
            traitsToDisable: args['traitsToDisable']
        };
    },
    rebuildMenuComponent: function(args){
        if (args.sourceKey === undefined){ throw "ECS fail " + args.key + " missing sourceKey"; }
        return {
            getRebuildButton: getRebuildButton,
            rebuildFunction: undefined,
            getRebuildFunction: getRebuildFunction,
            sourceKey: args['sourceKey']
        };
    },
    memoryGoalButton: function(args){
        args["managerKey"] = args.sourceKey;
        args["subPanel"] = (args["subPanel"] === undefined) ? "Goals" : args["subPanel"];
        return {
            uiActionComponent: ecs.buttonUiActionComponent(args),
            buttonControlComponent: ecs.goalButtonControlComponent(args),
            createExecutables: [ecs.killIfFlagExec(args), ecs.markTraitNew(args)],
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            uiDataComponent: ecs.uiDataComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    workChoreGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.workChoreGoal)));
        return ecs.worldGoal(args);
    },
    findCleanJobButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.findCleanJobButton)));
        return ecs.goalButton(args);
    },
    gameEndComponent: function(args){
        return {
            gameEnded: true
        };
    },
    painMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.painMine)));
        return ecs.memoryResource(args);
    },
    disavowalMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.disavowalMenu)));
        return ecs.goalCompleteMenu(args);
    },
    singleUseTrainPath: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.singleUseTrainPath)));
        return {
            rendererComponents: [ecs.trainPathTowerActiveRender(args), ecs.trainPathSecondRender(args), ecs.trainPathCircRenderComponent(args)],
            trainPathComponent: ecs.singleUseTrainPathComponent(args),
            menuCloseComponent: ecs.trainPathMenuCloseComponent(args),
            createExecutables: [ecs.setTrainColor(args)],
            killExecutables: [ecs.blinkTrainStops(args)],
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            focusCostComponent: ecs.focusCostComponent(args)
        };
    },
    toastClickComponent: function(args){
        if (args.panelKey === undefined){ throw "ECS fail " + args.key + " missing panelKey"; }
        return {
            onClick: toastClick,
            panelKey: args['panelKey']
        };
    },
    pairTransform: function(args){
        return {
            replaceMarker: ecs.pairTransformMarker(args)
        };
    },
    pathMoveComponent: function(args){
        return {
            startPosition: args['start'],
            ticks: 1,
            endPosition: args['end']
        };
    },
    panelRenderComponent: function(args){
        if (args.specificRender === undefined){ throw "ECS fail " + args.key + " missing specificRender"; }
        return {
            insetColor: "#000000",
            fullAnimationTime: 0.1,
            yTextOffset: 5,
            backImage: "uiBack",
            drawImageButton: drawImageButton,
            animatingTime: 0.1,
            priority: 18,
            render: outPanelRender,
            drawPanelButton: drawPanelButton,
            snapPanelPos: snapPanelPos,
            renderTime: 0,
            textColor: "#000000",
            renderState: "default",
            specificRender: args['specificRender'],
            drawHelpButton: drawHelpButton
        };
    },
    intelligent_ext: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.intelligent_ext)));
        return ecs.sourceExtension(args);
    },
    source: function(args){
        args["touchable"] = true;
        return {
            junctionComponent: ecs.junctionComponent(args),
            rendererComponents: [ecs.sourceRenderComponent(args)],
            clickComponent: ecs.fullMenuClickComponent(args),
            uniqueComponent: ecs.assertUniqueComponent(args),
            focusPathStopComponent: ecs.sourceFocusPathStopComponent(args),
            focusTickerComponent: ecs.focusTickerComponent(args),
            focusMakingComponent: (args.focusCount) ? ecs.focusMakingComponent(args) : undefined,
            interactionComponent: ecs.interactionComponent(args),
            healthComponent: ecs.healthComponent(args),
            creepAcceptComponent: ecs.creepAcceptComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.fixedPlayerMadeComponent(args)
        };
    },
    cleanButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cleanButton)));
        return ecs.towerButton(args);
    },
    dateComplete: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateComplete)));
        return ecs.randomReplace(args);
    },
    helpInfoPanelRenderComponent: function(args){
        if (args.helpLines === undefined){ throw "ECS fail " + args.key + " missing helpLines"; }
        return {
            drawPanelButton: drawPanelButton,
            insetColor: "#000000",
            fullAnimationTime: 0.1,
            yTextOffset: 5,
            backImage: "uiBack",
            drawImageButton: drawImageButton,
            animatingTime: 0.1,
            priority: 24,
            render: outPanelRender,
            helpLines: args['helpLines'],
            snapPanelPos: snapPanelPos,
            renderTime: 0,
            textColor: "#000000",
            renderState: "default",
            specificRender: helpInfoPanelRender,
            drawHelpButton: drawHelpButton
        };
    },
    fixedPlayerMadeComponent: function(args){
        return {
            playerKillable: false,
            canExpandInto: false
        };
    },
    disavowalGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.disavowalGoal)));
        return ecs.worldGoal(args);
    },
    tutorialDefenseTabTutorialTextComponent: function(args){
        return {
            entityToMark: args['entityToMark'],
            tutorialDone: tutorialDefenseTabTutorialDone,
            checkTutorialUiKey: checkTutorialUiKey,
            nextEntities: (args["nextEntities"] === undefined) ? undefined : args["nextEntities"]
        };
    },
    selfControlMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.selfControlMenu)));
        return ecs.goalCompleteMenu(args);
    },
    clickText: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.clickText)));
        return {
            rendererComponents: [ecs.clickTextRenderComponent(args)],
            timedDeathComponent: ecs.timedDeathComponent(args),
            clickComponent: ecs.clickKillComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args)
        };
    },
    joyMap: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.joyMap)));
        return ecs.emotionResourceMap(args);
    },
    rendererSystemComponent: function(args){
        return {
            currentRenderState: "default"
        };
    },
    randomReplaceMarker: function(args){
        return {
            goodEntities: (args["goodEntities"] === undefined) ? [] : args["goodEntities"],
            badEntities: (args["badEntities"] === undefined) ? [] : args["badEntities"],
            addReplaceMarker: (args["addReplaceMarker"] === undefined) ? true : args["addReplaceMarker"],
            getReplaceKeys: randomReplaceGetReplaceKeys
        };
    },
    workCompetitionRenderComponent: function(args){
        return {
            priority: 8,
            renderState: "default",
            render: workCompetitionRender
        };
    },
    connectSource: function(args){
        return {
            replaceMarker: ecs.connectSourceReplaceMarker(args)
        };
    },
    findCleanJobGoalConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.findCleanJobGoalConstruction)));
        return ecs.construction(args);
    },
    dateGoodMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateGoodMine)));
        return ecs.emotionResource(args);
    },
    cheerMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cheerMineConstruction)));
        return ecs.memoryConstruction(args);
    },
    memoryIsaacEntityComponent: function(args){
        return {
            endLifecycle: endLifecycle,
            startLifecycle: memoryStartLifecycle,
            makeLifecycleEntities: makeLifecycleEntities,
            entitiesMade: []
        };
    },
    thoughtMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.thoughtMineConstruction)));
        return ecs.construction(args);
    },
    impressMineButton_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.impressMineButton_personB)));
        return ecs.personResourceButton(args);
    },
    memoryFade: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            timedDeathComponent: ecs.timedDeathComponent(args),
            memoryFadeComponent: ecs.memoryFadeComponent(args),
            killExecutables: [ecs.memoryFadeExec(args)]
        };
    },
    consequenceSadMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.consequenceSadMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    currencyNotificationComponent: function(args){
        if (args.notificationKey === undefined){ throw "ECS fail " + args.key + " missing notificationKey"; }
        return {
            notificationFunctions: simpleDict(args.notificationKey, addNotificationCurrency),
            serializeComponent: ecs.notificationSerializeComponent(args),
            notificationKey: args['notificationKey']
        };
    },
    createModifyComponent: function(args){
        return {
            createModifiers: (args["createModifiers"] === undefined) ? {} : args["createModifiers"]
        };
    },
    starParticleEffect: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            lifeSentinelComponent: ecs.sourceLifeSentinelComponent(args),
            rendererComponents: [ecs.starParticleEffectRenderComponent(args)]
        };
    },
    desire: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.desire)));
        return ecs.currency(args);
    },
    acceptFearMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.acceptFearMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    happyTraitMaker: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.happyTraitMaker)));
        return ecs.traitMaker(args);
    },
    workShiftGoalConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.workShiftGoalConstruction)));
        return ecs.construction(args);
    },
    cabTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cabTower)));
        return ecs.tower(args);
    },
    learnAboutMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.learnAboutMenu)));
        return ecs.goalCompleteMenu(args);
    },
    replaceMarker: function(args){
        if (args.getReplaceKeys === undefined){ throw "ECS fail " + args.key + " missing getReplaceKeys"; }
        return {
            getReplaceKeys: args['getReplaceKeys']
        };
    },
    killFindJob: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.killFindJob)));
        return ecs.entityKill(args);
    },
    sourceExtension: function(args){
        args["touchable"] = true;
        return {
            rendererComponents: [ecs.sourceExtensionRenderComponent(args)],
            personHeldComponent: (args.personHeld) ? ecs.personHeldComponent(args) : undefined,
            createExecutables: [ecs.makeExtraPathsExec(args)],
            sourceExtensionComponent: ecs.sourceExtensionComponent(args),
            clickComponent: ecs.sourceExtensionClickComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.fixedPlayerMadeComponent(args)
        };
    },
    buildPanelRenderComponent: function(args){
        return {
            drawImageButton: drawImageButton,
            textColor: "#000000",
            fullAnimationTime: 0.1,
            yTextOffset: 5,
            backImage: "opaqueBack",
            insetColor: "#000000",
            animatingTime: 0.1,
            priority: 19,
            render: buildPanelRender,
            drawPanelButton: drawPanelButton,
            snapPanelPos: snapPanelPos,
            demolishButtonFunction: undefined,
            renderTime: 0,
            demolishPathButtonFunction: undefined,
            settingsFunction: undefined,
            renderState: "default",
            drawHelpButton: drawHelpButton,
            specificRender: args['specificRender'],
            pathButtonFunction: undefined
        };
    },
    creepAcceptComponent: function(args){
        return {
            creepHitEntities: (args["creepHitEntities"] === undefined) ? PARAMETERS.creepHit : args["creepHitEntities"],
            creepAccept: creepAccept
        };
    },
    daydreamMine_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.daydreamMine_personD)));
        return ecs.personResource(args);
    },
    steamEscape: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.steamEscape)));
        return ecs.steamAchievement(args);
    },
    personResource: function(args){
        args["touchable"] = true;
        args["canExpandInto"] = false;
        return {
            focusPathStopComponent: ecs.resourceFocusPathStopComponent(args),
            rendererComponents: [ecs.pathStopRenderComponent(args)],
            focusTickResponseComponent: ecs.resourceFocusTickResponseComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            managedComponent: ecs.managedComponent(args),
            personHeldComponent: ecs.personHeldComponent(args),
            createExecutables: [ecs.makeExtraPathsExec(args)],
            junctionComponent: ecs.junctionComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            killExecutables: [ecs.killResourceCurrencies(args)],
            resourceComponent: ecs.resourceComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    tutorialTextWakeTrain: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialTextWakeTrain)));
        return ecs.menuTutorialText(args);
    },
    demolishButtonRenderComponent: function(args){
        return {
            priority: 2,
            renderState: "default",
            render: demolishButtonRender,
            demolishButtonFunction: undefined
        };
    },
    date_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.date_personD)));
        return ecs.personCurrency(args);
    },
    trainPathMakerHighlightRenderComponent: function(args){
        return {
            priority: 10,
            highlightImage: "activeOutline",
            renderState: "default",
            render: trainPathMakerHighlightRender,
            timeToBlink: 0.25
        };
    },
    resourceFocusTickResponseComponent: function(args){
        return {
            firstTick: true,
            emotionTags: (args["emotionTags"] === undefined) ? undefined : args["emotionTags"],
            destination: (args["destination"] === undefined) ? undefined : args["destination"],
            focusTick: resourceFocusTick,
            focusTickHook: trueFunction
        };
    },
    notifyEntityMakerNotificationComponent: function(args){
        if (args.count === undefined){ throw "ECS fail " + args.key + " missing count"; }
        if (args.entitiesToMake === undefined){ throw "ECS fail " + args.key + " missing entitiesToMake"; }
        if (args.notificationArgs === undefined){ throw "ECS fail " + args.key + " missing notificationArgs"; }
        return {
            count: args['count'],
            notificationArgs: args['notificationArgs'],
            serializeComponent: ecs.notificationSerializeComponent(args),
            notificationFunctions: simpleDict(args.notificationChecked, notifyEntityMakerFn),
            impulseCount: args['count'],
            entitiesToMake: args['entitiesToMake']
        };
    },
    killTowerButtonExec: function(args){
        return {
            execute: killTowerButtonExec,
            killTowersOnDeath: (args["killTowersOnDeath"] === undefined) ? false : args["killTowersOnDeath"]
        };
    },
    qualityResource: function(args){
        args["touchable"] = true;
        args["canExpandInto"] = false;
        args["quality"] = (args["quality"] === undefined) ? Math.floor(Math.random() * 3) + 2 : args["quality"];
        return {
            focusPathStopComponent: ecs.resourceFocusPathStopComponent(args),
            rendererComponents: [ecs.pathStopRenderComponent(args)].concat([ecs.qualityRenderComponent(args)]),
            qualityComponent: ecs.qualityComponent(args),
            focusTickResponseComponent: ecs.resourceFocusTickResponseComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            managedComponent: ecs.managedComponent(args),
            createExecutables: [ecs.makeExtraPathsExec(args)],
            resourceComponent: ecs.resourceComponent(args),
            junctionComponent: ecs.junctionComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            killExecutables: [ecs.killResourceCurrencies(args)],
            tickComponent: ecs.qualityResourceTickComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    homeThoughtMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.homeThoughtMineConstruction)));
        return ecs.construction(args);
    },
    personDPathCity: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.personDPathCity)));
        return ecs.pathMaker(args);
    },
    connectSourceReplaceMarker: function(args){
        if (args.sourceId === undefined){ throw "ECS fail " + args.key + " missing sourceId"; }
        return {
            sourceId: args['sourceId'],
            getReplaceKeys: connectSourceGetReplaceKeys
        };
    },
    understand: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.understand)));
        return ecs.currency(args);
    },
    spyTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.spyTower)));
        return ecs.tower(args);
    },
    stageChangeComponent: function(args){
        if (args.goalsToCheck === undefined){ throw "ECS fail " + args.key + " missing goalsToCheck"; }
        return {
            executables: [ecs.makeEntityComponent(args), ecs.killEntityComponent(args)],
            shouldChange: false,
            checkAndChange: checkAndChangeGameState,
            goalsToCheck: args['goalsToCheck']
        };
    },
    goalCompleteMenu: function(args){
        args["y"] = 5;
        args["x"] = width / 2 - 325;
        args["size"] = [650, 600];
        return {
            rendererComponents: [ecs.goalCompleteMenuRender(args)],
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            uiClickComponent: ecs.panelUiClickComponent(args),
            createExecutables: [ecs.goalCompleteMenuCreateExec(args)],
            goalCompleteMenuComponent: ecs.goalCompleteMenuComponent(args),
            panelComponent: ecs.goalCompleteMenuPanelComponent(args),
            pauseComponent: ecs.pauseComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            rebuildMenuComponent: ecs.rebuildMenuComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    programmingMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.programmingMine)));
        return ecs.emotionResource(args);
    },
    memory_ringExcite: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_ringExcite)));
        return ecs.memory(args);
    },
    disableMemories: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.disableMemories)));
        args["name"] = "You're just too overwhelmed to feel this right now.";
        return {
            disableTraitComponent: ecs.disableMemoriesComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            tickComponent: ecs.disableMemoriesTickComponent(args),
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined
        };
    },
    killEntity: function(args){
        return {
            execute: killEntity
        };
    },
    sadTraitMaker: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.sadTraitMaker)));
        return ecs.traitMaker(args);
    },
    emotionResourceMap: function(args){
        return {
            emotionResourceMapComponent: ecs.emotionResourceMapComponent(args)
        };
    },
    pain: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.pain)));
        return ecs.currency(args);
    },
    understandMemoryMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.understandMemoryMenu)));
        return ecs.goalCompleteMenu(args);
    },
    dayResponseComponent: function(args){
        return {
            dayEnd: (args["dayEnd"] === undefined) ? undefined : args["dayEnd"],
            dayStart: (args["dayStart"] === undefined) ? undefined : args["dayStart"],
            dayChange: (args["dayChange"] === undefined) ? undefined : args["dayChange"]
        };
    },
    creep: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.creep)));
        return {
            pathVelocityComponent: ecs.pathVelocityComponent(args),
            rendererComponents: [ecs.creepRenderComponent(args), ecs.hitRenderComponent(args)],
            damageableComponent: ecs.damageableComponent(args),
            creepComponent: ecs.creepComponent(args),
            createExecutables: [ecs.creepCreateExec(args)],
            killExecutables: [ecs.creepDeathExec(args)],
            tickComponent: ecs.creepTickComponent(args),
            healthComponent: ecs.healthComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            dayResponseComponent: ecs.currencyDayResponseComponent(args),
            loseControlExec: ecs.killEntity(args)
        };
    },
    upgradeButton: function(args){
        args["subPanel"] = (args["subPanel"] === undefined) ? "Upgrades" : args["subPanel"];
        args["multiTrait"] = (args["multiTrait"] === undefined) ? false : args["multiTrait"];
        return {
            interactionComponent: ecs.interactionComponent(args),
            uiActionComponent: ecs.buttonUiActionComponent(args),
            buttonControlComponent: ecs.upgradeControlComponent(args),
            createExecutables: [ecs.markTraitNew(args)],
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            uiDataComponent: ecs.uiDataComponent(args),
            uniqueComponent: (args.timeToDeath === undefined && !args.multiTrait) ? ecs.uniqueComponent(args) : undefined,
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            killExecutables: [ecs.killTowerButtonExec(args)],
            identityComponent: ecs.identityComponent(args)
        };
    },
    cleanWorkHouseMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cleanWorkHouseMenu)));
        return ecs.goalCompleteMenu(args);
    },
    isaacEntityComponent: function(args){
        return {
            endLifecycle: endLifecycle,
            startLifecycle: makeIsaacLifecycleEntities,
            makeLifecycleEntities: makeLifecycleEntities,
            entitiesMade: [],
            isaacEntities: (args["isaacEntities"] === undefined) ? [] : args["isaacEntities"]
        };
    },
    memory_defineSelf: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_defineSelf)));
        return ecs.memory(args);
    },
    acquaintance: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.acquaintance)));
        return ecs.opinion(args);
    },
    selfExtension: function(args){
        return {
            rendererComponents: [ecs.selfExtensionRender(args)],
            sourceExtensionComponent: ecs.sourceExtensionComponent(args),
            selfExtensionComponent: ecs.selfExtensionComponent(args),
            tickComponent: ecs.selfExtensionTickComponent(args),
            positionComponent: ecs.positionComponent(args)
        };
    },
    tutorialTextAdultPanelComponent: function(args){
        return {
            entityToMark: args['entityToMark'],
            tutorialDone: tutorialTextAdultPanelDone,
            checkTutorialUiKey: checkTutorialUiKey,
            nextEntities: (args["nextEntities"] === undefined) ? undefined : args["nextEntities"]
        };
    },
    menuTutorialText: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tutorialTextComponent: ecs.menuTutorialTextComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            rendererComponents: [ecs.menuTutorialTextRenderComponent(args)]
        };
    },
    contactTower_personDConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.contactTower_personDConstruction)));
        return ecs.construction(args);
    },
    damageRenderComponent: function(args){
        if (args.image === undefined){ throw "ECS fail " + args.key + " missing image"; }
        return {
            priority: 7,
            image: args['image'],
            renderState: "default",
            render: damageRender,
            blinkTimer: 0
        };
    },
    steamAchievementComponent: function(args){
        if (args.keyToCheck === undefined){ throw "ECS fail " + args.key + " missing keyToCheck"; }
        if (args.steamAchievementKey === undefined){ throw "ECS fail " + args.key + " missing steamAchievementKey"; }
        return {
            steamAchievementKey: args['steamAchievementKey'],
            keyToCheck: args['keyToCheck']
        };
    },
    memoryFadeComponent: function(args){
        if (args.sourceKey === undefined){ throw "ECS fail " + args.key + " missing sourceKey"; }
        return {
            sourceKey: args['sourceKey']
        };
    },
    travelAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.travelAction)));
        return ecs.currencyAction(args);
    },
    thinkAboutTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.thinkAboutTower)));
        return ecs.tower(args);
    },
    tickComponent: function(args){
        return {
            tick: (args["tick"] === undefined) ? args['trueFunction'] : args["tick"]
        };
    },
    newTraitParticle: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.newTraitParticle)));
        return ecs.traitParticleEffect(args);
    },
    chessTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chessTowerConstruction)));
        return ecs.construction(args);
    },
    cheerMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cheerMine)));
        return ecs.memoryResource(args);
    },
    difficultySetting: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.difficultySetting)));
        return {
            identityComponent: ecs.identityComponent(args),
            difficultySettingComponent: ecs.difficultySettingComponent(args)
        };
    },
    opinionConditionalMake: function(args){
        return {
            healthComponent: ecs.healthComponent(args),
            createExecutables: [ecs.opinionConditionalExec(args), ecs.killEntity(args)]
        };
    },
    allBuildingPanelRenderComponent: function(args){
        return {
            insetColor: "#000000",
            fullAnimationTime: 0.1,
            yTextOffset: 5,
            backImage: "uiBack",
            renderTime: 0,
            drawImageButton: drawImageButton,
            animatingTime: 0.1,
            pageDownFunction: undefined,
            priority: 18,
            render: outPanelRender,
            drawPanelButton: drawPanelButton,
            snapPanelPos: snapPanelPos,
            setCategoryFn: {},
            pageUpFunction: undefined,
            currentCategory: undefined,
            buttonGroupCount: 1,
            textColor: "#000000",
            renderState: "default",
            page: 1,
            specificRender: allBuildingPanelRender,
            drawHelpButton: drawHelpButton
        };
    },
    contactAction_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.contactAction_personB)));
        return ecs.personCurrencyAction(args);
    },
    emotionTickModifyComponent: function(args){
        if (args.towerTrait === undefined){ throw "ECS fail " + args.key + " missing towerTrait"; }
        if (args.emotionState === undefined){ throw "ECS fail " + args.key + " missing emotionState"; }
        if (args.factor === undefined){ throw "ECS fail " + args.key + " missing factor"; }
        return {
            towerTrait: args['towerTrait'],
            emotionState: args['emotionState'],
            factor: args['factor']
        };
    },
    clickTextRenderComponent: function(args){
        return {
            priority: 9,
            renderState: "default",
            render: clickTextRender
        };
    },
    cleanWorkHouseGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cleanWorkHouseGoal)));
        return ecs.worldGoal(args);
    },
    gainControl_anger: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.gainControl_anger)));
        return ecs.gainControlMenu(args);
    },
    betrayal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.betrayal)));
        return ecs.emotionSource(args);
    },
    buyEscapeGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.buyEscapeGoal)));
        return ecs.worldGoal(args);
    },
    playerTriggeredNotification: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.IdentityComponent(args),
            notifyComponent: ecs.notifyComponent(args)
        };
    },
    planTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.planTower)));
        return ecs.tower(args);
    },
    improveQualityExec: function(args){
        return {
            qualityDiff: (args["qualityDiff"] === undefined) ? 1 : args["qualityDiff"],
            execute: improveQuality,
            checkAgainstQuality: (args["checkAgainstQuality"] === undefined) ? true : args["checkAgainstQuality"],
            entitiesToMake: (args["entitiesToMake"] === undefined) ? undefined : args["entitiesToMake"],
            qualityEntity: (args["qualityEntity"] === undefined) ? undefined : args["qualityEntity"]
        };
    },
    holdControlComponent: function(args){
        return {
            holdControl: true
        };
    },
    createModifyEntity: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            createModifyComponent: ecs.createModifyComponent(args)
        };
    },
    fantasyMine_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fantasyMine_personD)));
        return ecs.personResource(args);
    },
    uniqueComponent: function(args){
        return {
            uniqueDuplicateEvent: falseFunction
        };
    },
    learnFirstMissionGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.learnFirstMissionGoal)));
        return ecs.worldGoal(args);
    },
    fantasyMine_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fantasyMine_personB)));
        return ecs.personResource(args);
    },
    cabButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cabButton)));
        return ecs.towerButton(args);
    },
    empathizeButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.empathizeButton)));
        return ecs.memoryTowerButton(args);
    },
    homeThoughtMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.homeThoughtMine)));
        return ecs.resource(args);
    },
    tutorialFinish: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialFinish)));
        return ecs.tutorialUnpause(args);
    },
    memory_fearFlash: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_fearFlash)));
        return ecs.memory(args);
    },
    dateFailMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateFailMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    lifeThreat: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.lifeThreat)));
        return ecs.emotionSource(args);
    },
    personCurrency: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.personCurrency)));
        args["tags"] = (args.tags) ? args.tags.concat([args.key]) : [args.key];
        return {
            pathVelocityComponent: ecs.pathVelocityComponent(args),
            rendererComponents: [ecs.currencyRenderComponent(args)],
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            dayResponseComponent: (args.checkDayEnd) ? ecs.currencyDayResponseComponent(args) : undefined
        };
    },
    industry: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.industry)));
        return ecs.currency(args);
    },
    cleanCompeteCheck: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cleanCompeteCheck)));
        return ecs.notifyEntityMaker(args);
    },
    openMindMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.openMindMenu)));
        return ecs.goalCompleteMenu(args);
    },
    cabTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cabTowerConstruction)));
        return ecs.construction(args);
    },
    knownAgent: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.knownAgent)));
        return ecs.opinion(args);
    },
    goalCompleteMenuPanelComponent: function(args){
        return {
            panelButtons: [],
            expandPanel: expandPanel,
            minimized: false,
            canClose: true,
            closed: false,
            ignoreWhenMinimized: false
        };
    },
    bombPrep3Menu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.bombPrep3Menu)));
        return ecs.goalCompleteMenu(args);
    },
    nextDayEntityDayResponseComponent: function(args){
        return {
            dayChange: (args["dayChange"] === undefined) ? undefined : args["dayChange"],
            dayEnd: nextDayEntityDayEnd,
            dayStart: (args["dayStart"] === undefined) ? undefined : args["dayStart"],
            execs: [ecs.makeEntityComponent(args), ecs.killEntityComponent(args)]
        };
    },
    makeGainControlMenuExec: function(args){
        if (args.emotion === undefined){ throw "ECS fail " + args.key + " missing emotion"; }
        return {
            emotion: args['emotion'],
            execute: makeGainControlMenuExec,
            gainControlMenuKey: "gainControl"
        };
    },
    conditionalMakeReplaceMarker: function(args){
        if (args.entitiesToMake === undefined){ throw "ECS fail " + args.key + " missing entitiesToMake"; }
        if (args.stopEntities === undefined){ throw "ECS fail " + args.key + " missing stopEntities"; }
        return {
            entitiesToMake: args['entitiesToMake'],
            stopEntities: args['stopEntities'],
            getReplaceKeys: conditionalMakeGetReplaceKeys
        };
    },
    betrayalMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.betrayalMine)));
        return ecs.emotionResource(args);
    },
    buyTimeGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.buyTimeGoal)));
        return ecs.personGoal(args);
    },
    memory_prideJoy: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_prideJoy)));
        return ecs.memory(args);
    },
    cleanTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cleanTower)));
        return ecs.tower(args);
    },
    motherland: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.motherland)));
        return ecs.trait(args);
    },
    cityPathAdult: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cityPathAdult)));
        return ecs.pathMaker(args);
    },
    impressMineButton_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.impressMineButton_personD)));
        return ecs.personResourceButton(args);
    },
    fantasyMineButton_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fantasyMineButton_personD)));
        return ecs.personResourceButton(args);
    },
    contactTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.contactTower)));
        return ecs.tower(args);
    },
    cleanChoreGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cleanChoreGoal)));
        return ecs.worldGoal(args);
    },
    tutorialUnpauseTextComponent: function(args){
        return {
            tutorialDone: tutorialUnpauseTutorialDone,
            nextEntities: (args["nextEntities"] === undefined) ? undefined : args["nextEntities"]
        };
    },
    askOutButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.askOutButton)));
        return ecs.personGoalButton(args);
    },
    entityLifecycleComponent: function(args){
        if (args.startLifecycle === undefined){ throw "ECS fail " + args.key + " missing startLifecycle"; }
        return {
            endLifecycle: endLifecycle,
            startLifecycle: args['startLifecycle'],
            makeLifecycleEntities: makeLifecycleEntities,
            entitiesMade: []
        };
    },
    buyTimeButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.buyTimeButton)));
        return ecs.personGoalButton(args);
    },
    utilityPanelAbsolutePositionComponent: function(args){
        return {
            y: 678,
            x: 600,
            size: [250, 90]
        };
    },
    dateButton_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateButton_personD)));
        return ecs.personTowerButton(args);
    },
    confession: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confession)));
        return ecs.currency(args);
    },
    understandMemoryGoalConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.understandMemoryGoalConstruction)));
        return ecs.memoryConstruction(args);
    },
    setTrainColor: function(args){
        return {
            execute: setTrainColorExec
        };
    },
    missHomeMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.missHomeMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    trainPanelRenderComponent: function(args){
        return {
            arrowImage: "trainPathArrow",
            insetColor: "#000000",
            fullAnimationTime: 0.1,
            yTextOffset: 5,
            backImage: "uiBack",
            isConnectingTrack: false,
            drawImageButton: drawImageButton,
            animatingTime: 0.1,
            priority: 18,
            render: outPanelRender,
            drawPanelButton: drawPanelButton,
            snapPanelPos: snapPanelPos,
            renderTime: 0,
            textColor: "#000000",
            renderState: "default",
            specificRender: trainPanelRender,
            drawHelpButton: drawHelpButton
        };
    },
    dailyHelpInfoPanel: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dailyHelpInfoPanel)));
        return ecs.helpInfoPanel(args);
    },
    aliveMakeComponent: function(args){
        return {
            focusTick: aliveMakeFocusTick,
            aliveEntities: (args["aliveEntities"] === undefined) ? [] : args["aliveEntities"]
        };
    },
    fightProgramGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.fightProgramGoal)));
        return ecs.worldGoal(args);
    },
    cabAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cabAction)));
        return ecs.currencyAction(args);
    },
    uiDataComponent: function(args){
        if (args.image === undefined){ throw "ECS fail " + args.key + " missing image"; }
        return {
            subPanel: (args["subPanel"] === undefined) ? undefined : args["subPanel"],
            image: args['image'],
            drawImage: uiDataDrawImage,
            lastPath: undefined
        };
    },
    painMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.painMineConstruction)));
        return ecs.memoryConstruction(args);
    },
    askAboutGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.askAboutGoal)));
        return ecs.personGoal(args);
    },
    healthModifyComponent: function(args){
        return {
            idleHealthChange: -10,
            activeHealthChange: 30,
            activated: false,
            healthModifyAmount: healthModifyAmount
        };
    },
    pathMakerReplaceMarker: function(args){
        if (args.startKey === undefined){ throw "ECS fail " + args.key + " missing startKey"; }
        if (args.endKey === undefined){ throw "ECS fail " + args.key + " missing endKey"; }
        return {
            startKey: args['startKey'],
            endKey: args['endKey'],
            getReplaceKeys: pathMakerGetReplaceKeys
        };
    },
    towerTickModifier: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.towerTickModifier)));
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tickModifyComponent: ecs.tickModifyComponent(args),
            rendererComponents: [ecs.towerTickModifierRenderComponent(args)],
            dayResponseComponent: ecs.killDayEndComponent(args),
            timedDeathComponent: ecs.timedDeathComponent(args)
        };
    },
    driveAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.driveAction)));
        return ecs.currencyAction(args);
    },
    dayCounterComponent: function(args){
        args["ticks"] = PARAMETERS.dayTicks;
        if (args.ticks === undefined){ throw "ECS fail " + args.key + " missing ticks"; }
        return {
            dayCount: (args["dayCount"] === undefined) ? 1 : args["dayCount"],
            ticks: args['ticks'],
            getPartOfDay: getPartOfDay,
            impulseTicks: args['ticks']
        };
    },
    killedMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.killedMenu)));
        return ecs.goalCompleteMenu(args);
    },
    dayKillExec: function(args){
        return {
            execute: dayKillExec
        };
    },
    makeTarget: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.makeTarget)));
        return ecs.addPersonAndOpinion(args);
    },
    frustration: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.frustration)));
        return ecs.currency(args);
    },
    killTrain: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            createExecutables: [ecs.killTrainExec(args), ecs.killEntity(args)]
        };
    },
    focusCostComponent: function(args){
        return {
            focusCost: (args["focusCost"] === undefined) ? 1 : args["focusCost"]
        };
    },
    traitHelpInfoPanel: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.traitHelpInfoPanel)));
        return ecs.helpInfoPanel(args);
    },
    travelButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.travelButton)));
        return ecs.towerButton(args);
    },
    energy: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.energy)));
        return ecs.currency(args);
    },
    toastRenderComponent: function(args){
        return {
            priority: 18,
            renderState: "default",
            render: toastRender
        };
    },
    flashback_joy: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flashback_joy)));
        return ecs.emotionResource(args);
    },
    swallowOpinionCreate: function(args){
        return {
            execute: swallowOpinionCreate,
            swallowExceptions: (args["swallowExceptions"] === undefined) ? [] : args["swallowExceptions"]
        };
    },
    planAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.planAction)));
        return ecs.currencyAction(args);
    },
    restaurantPrepMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.restaurantPrepMenu)));
        return ecs.goalCompleteMenu(args);
    },
    makeAll: function(args){
        return {
            replaceMarker: ecs.makeAllReplaceMarker(args)
        };
    },
    missHome: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.missHome)));
        return ecs.emotionSource(args);
    },
    makeRandomMultipleReplaceMarker: function(args){
        if (args.count === undefined){ throw "ECS fail " + args.key + " missing count"; }
        if (args.entitiesToMake === undefined){ throw "ECS fail " + args.key + " missing entitiesToMake"; }
        return {
            count: args['count'],
            minCount: (args["minCount"] === undefined) ? 1 : args["minCount"],
            entitiesToMake: args['entitiesToMake'],
            getReplaceKeys: makeRandomMultipleGetReplaceKeys
        };
    },
    particleEffect: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            lifeSentinelComponent: ecs.sourceLifeSentinelComponent(args),
            rendererComponents: [ecs.particleEffectRenderComponent(args)]
        };
    },
    emotionSource: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            emotionSourceComponent: ecs.emotionSourceComponent(args),
            createExecutables: [ecs.emotionSourceCreateExec(args), ecs.killEntity(args)]
        };
    },
    timedCreateEntities: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            timedDeathComponent: ecs.timedDeathComponent(args)
        };
    },
    entityReplace: function(args){
        return {
            replaceMarker: ecs.entityReplaceMarker(args)
        };
    },
    painMineButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.painMineButton)));
        return ecs.memoryResourceButton(args);
    },
    trainPathMenuCloseComponent: function(args){
        return {
            execute: trainPathMenuCloseExec
        };
    },
    depositFocusPathStopComponent: function(args){
        return {
            interactWithTrain: depositInteractWithTrain,
            currenciesNeededAtPoint: (args["currenciesNeededAtPoint"] === undefined) ? emptyArrayFunction : args["currenciesNeededAtPoint"],
            currenciesMadeAtPoint: depositCurrenciesMadeAtPoint,
            isDisabled: false,
            currenciesExistAtPoint: currenciesExistAtPoint,
            updateCurrencyDictWithStop: updateCurrencyDictWithStop,
            isPathEnd: (args["isPathEnd"] === undefined) ? false : args["isPathEnd"],
            checkFocusStopDisabled: checkFocusStopDisabled,
            requiredOpinions: (args["requiredOpinions"] === undefined) ? [] : args["requiredOpinions"],
            enableChecks: (args["enableChecks"] === undefined) ? [] : args["enableChecks"]
        };
    },
    energyMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.energyMine)));
        return ecs.resource(args);
    },
    restaurantDateGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.restaurantDateGoal)));
        return ecs.personGoal(args);
    },
    selfDamageEffect: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            lifeSentinelComponent: ecs.sourceLifeSentinelComponent(args),
            rendererComponents: [ecs.selfDamageEffectRenderComponent(args)]
        };
    },
    trainPathHelpInfoPanel: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.trainPathHelpInfoPanel)));
        return ecs.helpInfoPanel(args);
    },
    dateGood: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateGood)));
        return ecs.emotionSource(args);
    },
    dateGoodMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateGoodMineConstruction)));
        return ecs.emotionSourceConstruction(args);
    },
    flareMemoryGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flareMemoryGoal)));
        return ecs.memoryGoal(args);
    },
    flareMemoryGoalConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.flareMemoryGoalConstruction)));
        return ecs.memoryConstruction(args);
    },
    traitMenuCloseComponent: function(args){
        return {
            execute: traitMenuCloseExec
        };
    },
    cabPrideCheck: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cabPrideCheck)));
        return ecs.notifyEntityMaker(args);
    },
    understandButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.understandButton)));
        return ecs.towerButton(args);
    },
    learnFirstMissionMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.learnFirstMissionMenu)));
        return ecs.goalCompleteMenu(args);
    },
    findCabJobMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.findCabJobMenu)));
        return ecs.goalCompleteMenu(args);
    },
    personTower: function(args){
        args["touchable"] = true;
        args["canExpandInto"] = false;
        args["isPathEnd"] = (args["isPathEnd"] === undefined) ? (args.damageMultipliers ? true : false) : args["isPathEnd"];
        args["tags"] = (args["tags"] === undefined) ? [args.key] : args["tags"];
        return {
            focusPathStopComponent: ecs.towerFocusPathStopComponent(args),
            rendererComponents: [ecs.pathStopRenderComponent(args), ecs.damageRenderComponent(args)],
            focusTickResponseComponent: ecs.towerFocusTickResponseComponent(args),
            healthModifyComponent: ecs.healthModifyComponent(args),
            personHeldComponent: ecs.personHeldComponent(args),
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            damageComponent: (args.damageMultipliers) ? ecs.damageComponent(args) : undefined,
            managedComponent: ecs.managedComponent(args),
            createExecutables: [ecs.makeExtraPathsExec(args)].concat([ecs.towerCreateExec(args)]),
            junctionComponent: ecs.junctionComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            tickComponent: ecs.towerTickComponent(args),
            towerComponent: ecs.towerComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    workCompetition: function(args){
        return {
            rendererComponents: [ecs.workCompetitionRenderComponent(args)],
            workCompetitionComponent: ecs.workCompetitionComponent(args),
            createExecutables: [ecs.randomOpenPositionExec(args)],
            tickComponent: ecs.workCompetitionTickComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            notificationComponents: [ecs.workCompetitionNotificationComponent(args)]
        };
    },
    daydreamMineButton_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.daydreamMineButton_personD)));
        return ecs.personResourceButton(args);
    },
    findCabJobGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.findCabJobGoal)));
        return ecs.worldGoal(args);
    },
    memory_acceptFear: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.memory_acceptFear)));
        return ecs.memory(args);
    },
    daydreamMineButton_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.daydreamMineButton_personB)));
        return ecs.personResourceButton(args);
    },
    bombPrep2Goal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.bombPrep2Goal)));
        return ecs.worldGoal(args);
    },
    trainPathMakingPanel: function(args){
        args["y"] = 5;
        args["x"] = width - 355;
        args["key"] = PARAMETERS.trainPathMakingPanel;
        args["size"] = [350, 300];
        return {
            rendererComponents: [ecs.trainPathMakingPanelRender(args)],
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            uiClickComponent: ecs.panelUiClickComponent(args),
            panelComponent: ecs.panelComponent(args),
            pauseComponent: ecs.pauseComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    tutorialBuildPaint2: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialBuildPaint2)));
        return ecs.menuTutorialText(args);
    },
    findCabJobButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.findCabJobButton)));
        return ecs.goalButton(args);
    },
    emotionSourceConstructionCreateExec: function(args){
        if (args.sourceKey === undefined){ throw "ECS fail " + args.key + " missing sourceKey"; }
        return {
            focusStopKey: "impulse",
            execute: emotionSourceConstructionCreateExec,
            trainPath: PARAMETERS.singleUseTrainPath,
            sourceKey: args['sourceKey']
        };
    },
    disavowalTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.disavowalTower)));
        return ecs.tower(args);
    },
    worldGoalParticleEffectLifeSentinelComponent: function(args){
        if (args.sourceId === undefined){ throw "ECS fail " + args.key + " missing sourceId"; }
        return {
            sourceId: args['sourceId'],
            checkAlive: checkWorldGoalParticleEffectAlive
        };
    },
    placedTowerReplaceMarker: function(args){
        if (args.tower === undefined){ throw "ECS fail " + args.key + " missing tower"; }
        if (args.x === undefined){ throw "ECS fail " + args.key + " missing x"; }
        if (args.y === undefined){ throw "ECS fail " + args.key + " missing y"; }
        if (args.sourceTrait === undefined){ throw "ECS fail " + args.key + " missing sourceTrait"; }
        return {
            y: args['y'],
            x: args['x'],
            tower: args['tower'],
            sourceTrait: args['sourceTrait'],
            getReplaceKeys: placedTowerGetReplaceKeys
        };
    },
    askOutGoalConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.askOutGoalConstruction)));
        return ecs.construction(args);
    },
    contactTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.contactTowerConstruction)));
        return ecs.construction(args);
    },
    confessTower_personDConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confessTower_personDConstruction)));
        return ecs.construction(args);
    },
    towerCreateExec: function(args){
        return {
            execute: towerCreateExec
        };
    },
    confessAction_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.confessAction_personD)));
        return ecs.personCurrencyAction(args);
    },
    thoughtMineButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.thoughtMineButton)));
        return ecs.resourceButton(args);
    },
    supportButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.supportButton)));
        return ecs.personGoalButton(args);
    },
    chapterPanel: function(args){
        args["y"] = 5;
        args["x"] = 5;
        args["key"] = PARAMETERS.chapterPanel;
        args["size"] = [500, 300];
        return {
            rendererComponents: [ecs.chapterPanelRenderComponent(args)],
            absolutePositionComponent: ecs.absolutePositionComponent(args),
            uiClickComponent: ecs.panelUiClickComponent(args),
            panelComponent: ecs.panelComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            identityComponent: ecs.identityComponent(args)
        };
    },
    motherland_ext: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.motherland_ext)));
        return ecs.sourceExtension(args);
    },
    walkDateGoal: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.walkDateGoal)));
        return ecs.personGoal(args);
    },
    angerTickModifier: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.angerTickModifier)));
        args["tickModifyInteraction"] = angerTickModifyInteraction;
        return {
            healthComponent: ecs.singleHealthComponent(args),
            tickModifyComponent: ecs.tickModifyComponent(args),
            rendererComponents: [ecs.towerTickModifierRenderComponent(args)],
            dayResponseComponent: ecs.killDayEndComponent(args),
            timedDeathComponent: ecs.timedDeathComponent(args)
        };
    },
    sadnessResourceModifier: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.sadnessResourceModifier)));
        return ecs.resourceModifier(args);
    },
    currencyDayResponseComponent: function(args){
        return {
            dayEnd: currencyDayEnd,
            dayStart: (args["dayStart"] === undefined) ? undefined : args["dayStart"],
            dayChange: (args["dayChange"] === undefined) ? undefined : args["dayChange"]
        };
    },
    depositPositionExec: function(args){
        return {
            execute: depositPositionExec,
            randomPosition: (args["randomPosition"] === undefined) ? false : args["randomPosition"]
        };
    },
    managedComponent: function(args){
        if (args.sourceTrait === undefined){ throw "ECS fail " + args.key + " missing sourceTrait"; }
        return {
            managerKey: args['sourceTrait'],
            addExperienceToManager: addExperienceToManager,
            killOnTransform: (args["killOnTransform"] === undefined) ? false : args["killOnTransform"],
            isManagingEntity: isManagingEntity
        };
    },
    energyMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.energyMineConstruction)));
        return ecs.construction(args);
    },
    tutorialTextWakeUp: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialTextWakeUp)));
        return ecs.tutorialText(args);
    },
    swallowOpinion: function(args){
        args["isaacEntities"] = generateOpinionIsaacs(args);
        return {
            isaacEntityComponent: ecs.isaacEntityComponent(args),
            opinionComponent: ecs.opinionComponent(args),
            focusTickResponseComponent: (args.aliveEntities) ? ecs.aliveMakeComponent(args) : undefined,
            personHeldComponent: ecs.personHeldComponent(args),
            swallowOpinionComponent: ecs.swallowOpinionComponent(args),
            createExecutables: [ecs.swallowOpinionCreate(args), ecs.killPersonGoals(args)],
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            killExecutables: [ecs.swallowOpinionKill(args)],
            tickComponent: ecs.opinionTickComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            focusMakingComponent: (args.focusCount) ? ecs.focusMakingComponent(args) : undefined,
            identityComponent: ecs.identityComponent(args)
        };
    },
    joyTickModifier: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.joyTickModifier)));
        return ecs.towerTickModifier(args);
    },
    planButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.planButton)));
        return ecs.towerButton(args);
    },
    focusMakingComponent: function(args){
        if (args.focusCount === undefined){ throw "ECS fail " + args.key + " missing focusCount"; }
        return {
            ai: (args["ai"] === undefined) ? false : args["ai"],
            ticks: 2,
            focusEfficiency: focusEfficiency,
            impulseTicks: 22,
            focusCount: args['focusCount'],
            focusUsing: 0
        };
    },
    trait: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.trait)));
        args["touchable"] = true;
        args["health"] = (args["health"] === undefined) ? 2000 : args["health"];
        args["panelKey"] = (args["panelKey"] === undefined) ? PARAMETERS.traitPanel : args["panelKey"];
        return {
            killExecutables: [ecs.killExtensions(args)],
            isaacEntityComponent: ecs.isaacEntityComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            rendererComponents: [ecs.builtSourceRendererComponent(args)],
            uiActionComponent: ecs.buildingUiActionComponent(args),
            focusTickResponseComponent: (args.aliveEntities) ? ecs.focusTickAliveMakerComponent(args) : undefined,
            mapSpawnComponent: ecs.builtSourceMapSpawnComponent(args),
            experienceComponent: ecs.experienceComponent(args),
            personHeldComponent: (args.personHeld) ? ecs.personHeldComponent(args) : undefined,
            createExecutables: [ecs.builtSourceCreateExec(args), ecs.makeExtraPathsExec(args)],
            junctionComponent: ecs.junctionComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            entityManagerComponent: ecs.entityManagerComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.healthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            menuCloseComponent: ecs.traitMenuCloseComponent(args),
            playerMadeComponent: ecs.fixedPlayerMadeComponent(args)
        };
    },
    askAboutButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.askAboutButton)));
        return ecs.personGoalButton(args);
    },
    worker: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.worker)));
        args["tags"] = (args.tags) ? args.tags.concat([args.key]) : [args.key];
        return {
            pathVelocityComponent: ecs.pathVelocityComponent(args),
            rendererComponents: [ecs.workerRenderComponent(args)],
            healthComponent: ecs.singleHealthComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            dayResponseComponent: (args.checkDayEnd) ? ecs.currencyDayResponseComponent(args) : undefined
        };
    },
    personCurrencyAction: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            uniqueComponent: ecs.uniqueComponent(args),
            identityComponent: ecs.identityComponent(args),
            currencyActionComponent: ecs.currencyActionComponent(args)
        };
    },
    memoryGoalTrackComponent: function(args){
        return {
            successExecutables: [ecs.goalCompleteExec(args)].concat([ecs.trackActionables(args), ecs.makeEntityComponent(args), ecs.makeGoalMenuExecutable(args)]).concat([ecs.memoryGoalCompleteExec(args)]),
            checkCompleted: checkWorldGoalComponent
        };
    },
    date_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.date_personB)));
        return ecs.personCurrency(args);
    },
    tickModifyComponent: function(args){
        if (args.tickFactor === undefined){ throw "ECS fail " + args.key + " missing tickFactor"; }
        if (args.towerId === undefined){ throw "ECS fail " + args.key + " missing towerId"; }
        if (args.distractFactor === undefined){ throw "ECS fail " + args.key + " missing distractFactor"; }
        return {
            tickFactor: args['tickFactor'],
            distractFactor: args['distractFactor'],
            tickModifyInteraction: (args["tickModifyInteraction"] === undefined) ? trueFunction : args["tickModifyInteraction"],
            towerId: args['towerId']
        };
    },
    emotionResource: function(args){
        args["touchable"] = true;
        args["canExpandInto"] = false;
        args["playerKillable"] = false;
        return {
            focusPathStopComponent: ecs.resourceFocusPathStopComponent(args),
            rendererComponents: [ecs.pathStopRenderComponent(args)],
            focusTickResponseComponent: ecs.resourceFocusTickResponseComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            managedComponent: ecs.managedComponent(args),
            createExecutables: [ecs.makeExtraPathsExec(args)].concat([ecs.emotionResourceDifficultyExec(args)]),
            junctionComponent: ecs.junctionComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            killExecutables: [ecs.killResourceCurrencies(args)],
            resourceComponent: ecs.resourceComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            loseControlExec: ecs.killEntity(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    entityPanelRenderComponent: function(args){
        if (args.entityImage === undefined){ throw "ECS fail " + args.key + " missing entityImage"; }
        return {
            drawImageButton: drawImageButton,
            fullAnimationTime: 0.1,
            yTextOffset: 5,
            backImage: "uiBack",
            entityImage: args['entityImage'],
            insetColor: "#000000",
            animatingTime: 0.1,
            priority: 18,
            render: outPanelRender,
            drawPanelButton: drawPanelButton,
            snapPanelPos: snapPanelPos,
            renderTime: 0,
            textColor: "#000000",
            renderState: "default",
            specificRender: entityPanelRender,
            drawHelpButton: drawHelpButton
        };
    },
    chessProject: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.chessProject)));
        return ecs.project(args);
    },
    spyResult: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.spyResult)));
        return ecs.nextDayEntity(args);
    },
    openMenu: function(args){
        return {
            execute: openMenuExecute,
            markerKey: "openMenuMarker"
        };
    },
    spellRenderComponent: function(args){
        return {
            priority: 9,
            renderState: "default",
            render: spellRender
        };
    },
    tutorialTextWakeUpPanel: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialTextWakeUpPanel)));
        return ecs.menuTutorialText(args);
    },
    cabChore: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cabChore)));
        return ecs.dailyEntityMaker(args);
    },
    talkAction_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.talkAction_personD)));
        return ecs.personCurrencyAction(args);
    },
    supportMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.supportMenu)));
        return ecs.goalCompleteMenu(args);
    },
    talkAction_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.talkAction_personB)));
        return ecs.personCurrencyAction(args);
    },
    dateAction_personD: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateAction_personD)));
        return ecs.personCurrencyAction(args);
    },
    impressMine_personBConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.impressMine_personBConstruction)));
        return ecs.construction(args);
    },
    askOutSuccess: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.askOutSuccess)));
        return ecs.emotionSource(args);
    },
    walkTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.walkTowerConstruction)));
        return ecs.construction(args);
    },
    currencyRequest: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            positionComponent: ecs.positionComponent(args),
            requestComponent: ecs.requestComponent(args)
        };
    },
    building: function(args){
        args["touchable"] = true;
        args["canExpandInto"] = false;
        return {
            managedComponent: ecs.managedComponent(args),
            createExecutables: [ecs.makeExtraPathsExec(args)],
            junctionComponent: ecs.junctionComponent(args),
            clickComponent: ecs.fullMenuClickComponent(args),
            uiDataComponent: ecs.uiDataComponent(args),
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            interactionComponent: ecs.interactionComponent(args),
            identityComponent: ecs.identityComponent(args),
            positionComponent: ecs.positionComponent(args),
            playerMadeComponent: ecs.playerMadeComponent(args)
        };
    },
    askAboutMenu: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.askAboutMenu)));
        return ecs.goalCompleteMenu(args);
    },
    prepareTower: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.prepareTower)));
        return ecs.memoryTower(args);
    },
    bullet: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            rendererComponents: [ecs.bulletRenderComponent(args)],
            tickComponent: ecs.bulletTickComponent(args)
        };
    },
    checkWorldGoalComponent: function(args){
        return {
            successExecutables: [ecs.goalCompleteExec(args)].concat([ecs.trackActionables(args), ecs.makeEntityComponent(args), ecs.makeGoalMenuExecutable(args)]),
            checkCompleted: checkWorldGoalComponent
        };
    },
    industryMineButton: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.industryMineButton)));
        return ecs.resourceButton(args);
    },
    spyTowerConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.spyTowerConstruction)));
        return ecs.construction(args);
    },
    killTrait: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            createExecutables: [ecs.killTraitExec(args), ecs.killEntity(args)]
        };
    },
    traitParticleEffectLifeSentinelComponent: function(args){
        if (args.managerKey === undefined){ throw "ECS fail " + args.key + " missing managerKey"; }
        return {
            managerKey: args['managerKey'],
            checkAlive: checkTraitParticleEffectAlive
        };
    },
    spyAction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.spyAction)));
        return ecs.currencyAction(args);
    },
    willMineConstruction: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.willMineConstruction)));
        return ecs.construction(args);
    },
    placedTower: function(args){
        return {
            replaceMarker: ecs.placedTowerReplaceMarker(args)
        };
    },
    upgradeControlComponent: function(args){
        if (args.baseKeys === undefined){ throw "ECS fail " + args.key + " missing baseKeys"; }
        if (args.entityKey === undefined){ throw "ECS fail " + args.key + " missing entityKey"; }
        if (args.constructionKey === undefined){ throw "ECS fail " + args.key + " missing constructionKey"; }
        return {
            aiVal: (args["aiVal"] === undefined) ? 0 : args["aiVal"],
            constructionKey: args['constructionKey'],
            checkMakePos: checkUpgradePos,
            baseKeys: args['baseKeys'],
            currenciesMade: (args["currenciesMade"] === undefined) ? [] : args["currenciesMade"],
            isNew: true,
            canMakeEntity: canMakeUpgrade,
            entityKey: args['entityKey'],
            wrappedFunction: undefined,
            managerKey: (args["managerKey"] === undefined) ? args.sourceKey : args["managerKey"],
            makeEntity: upgradeTower,
            getAiVal: controlComponentGetAiVal,
            getWrappedFunction: getWrappedFunction,
            baseUpgradeTowerForPos: baseUpgradeTowerForPos
        };
    },
    notification: function(args){
        return {
            healthComponent: ecs.singleHealthComponent(args),
            notifyComponent: ecs.notifyComponent(args)
        };
    },
    memoryFindPositionExec: function(args){
        if (args.startPos === undefined){ throw "ECS fail " + args.key + " missing startPos"; }
        return {
            execute: memoryFindPositionExec,
            startPos: args['startPos'],
            iterations: 25
        };
    },
    tutorialTextWakeUpFinish: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.tutorialTextWakeUpFinish)));
        return ecs.tutorialText(args);
    },
    disableMemoriesTickComponent: function(args){
        if (args.name === undefined){ throw "ECS fail " + args.key + " missing name"; }
        return {
            tick: disableMemoriesTick,
            name: args['name']
        };
    },
    cautionMine: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.cautionMine)));
        return ecs.memoryResource(args);
    },
    resourceButton: function(args){
        args["subPanel"] = (args["subPanel"] === undefined) ? "Towers" : args["subPanel"];
        args["multiTrait"] = (args["multiTrait"] === undefined) ? false : args["multiTrait"];
        return {
            interactionComponent: ecs.interactionComponent(args),
            uiActionComponent: ecs.buttonUiActionComponent(args),
            buttonControlComponent: ecs.towerControlComponent(args),
            createExecutables: [ecs.markTraitNew(args)],
            timedDeathComponent: (args.timeToDeath) ? ecs.timedDeathComponent(args) : undefined,
            uiDataComponent: ecs.uiDataComponent(args),
            uniqueComponent: (args.timeToDeath === undefined && !args.multiTrait) ? ecs.uniqueComponent(args) : undefined,
            descriptionComponent: ecs.descriptionComponent(args),
            healthComponent: ecs.singleHealthComponent(args),
            killExecutables: [ecs.killTowerButtonExec(args)],
            identityComponent: ecs.identityComponent(args)
        };
    },
    dateButton_personB: function(args){
        updateArgs(args, JSON.parse(JSON.stringify(allArgs.dateButton_personB)));
        return ecs.personTowerButton(args);
    }
};


var setAbsPosFromRect = function(rect){
    this.x = rect[0];
    this.y = rect[1];
    this.size[0] = rect[2];
    this.size[1] = rect[3];
};

var getAbsPosRect = function(){
    return [this.x, this.y, this.size[0], this.size[1]];
};


var activateRender = function(rect){
    if (this.activated){
        ctx.drawImage(getImage('activeHex'), rect[0], rect[1]);
        this.activated = false;
    }
}


var addPersonAndOpinionExec = function(entity, world){
    var person = world.entityForKey(this.personKey);
    if (!person){
        person = makeEcs(this.personKey);
        var pos = world.randomOpenPosition(person.positionComponent.planeOfExistence);
        person.positionComponent.x = pos[0];
        person.positionComponent.y = pos[1];
        world.addEntity(person);
    }

    person.personalityComponent.addOpinionToPersonality(this.opinionKey, person, world);
    entity.healthComponent.health = 0;
};


var aliveMakeFocusTick = function(entity, world){
    world.makeEntities(entity, this.aliveEntities);
};


var allBuildingPanelRender = function(rect, entity, ui){
    var buttonRect;
    if (!this.pageUpFunction){
        this.pageUpFunction = () => {
            this.page -= 1;
            this.page = max(this.page, 1);
            this.currentCategory = undefined;
        };

        this.pageDownFunction = () => {
            this.page += 1;
            this.currentCategory = undefined;
        };
    }

    var panelButtons = [];
    var world = ui.world;

    var buttons = _.chain(world.entities)
        .filter(function(worldEntity){
            return worldEntity.buttonControlComponent;
        })
        .groupBy(function(worldEntity){
            return worldEntity.buttonControlComponent.managerKey;
        })
        .value();

    var maxPage = Object.keys(buttons).length;
    this.page = min(this.page, maxPage);

    var lineCount = _.reduce(buttons, function(memo, val, key){
        return memo + val.length + 1;
    }, 0);
    
    entity.absolutePositionComponent.size[1] = 650;
    rect = entity.absolutePositionComponent.getAbsPosRect();

    ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);

    ctx.fillStyle = this.textColor;
    ctx.font = getFont(28);

    var title = 'Buildings';
    var titleWidth = ctx.measureText(title).width;

    ctx.fillText(title, rect[0] + (rect[2] - titleWidth) / 2, rect[1] + 60);

    var y = rect[1] + 150;
    ctx.font = getFont(18);
    ctx.strokeStyle = this.insetColor;

    var buttonsOnPage = 0;
    var traitPage = 0;

    _.each(buttons, (btns, trait) => {
        traitPage += 1;
        
        if (traitPage != this.page){
            return;
        }

        var traitImage = getImage(allArgs[trait].image);
        ctx.drawImage(traitImage, rect[0] + 10, y - 38);

        ctx.font = getFont(24);
        ctx.fillText(sanitize(trait), rect[0] + 80, y);
        ctx.font = getFont(18);
        y += 50;

        var traitEntity = world.entityForKey(trait);

        if (traitEntity.entityManagerComponent.disabled){
            var disablingEntity = _.chain(world.entities)
                .filter(worldEntity => (worldEntity.disableTraitComponent && 
                                        worldEntity.disableTraitComponent.checkTraitDisabled(traitEntity)))
                .first()
                .value();
            ctx.fillText('Disabled: ' + disablingEntity.identityComponent.name, rect[0] + 30, y);
            y += 50;
            return;
        }
        
        var categories = _.chain(btns)
            .map(function(button){ return button.uiDataComponent.subPanel; })
            .uniq()
            .sortBy(_.identity)
            .reverse()
            .value();

        if (this.currentCategory === undefined){
            this.currentCategory = categories[0];
        }

        _.each(categories, (panelKey, i) => {
            var x = rect[0] + 10 + 130 * i;
            if (panelKey == this.currentCategory){
                ctx.fillText(panelKey, x, y - 10);
                ctx.fillRect(x, y - 5, 70, 2);
            }
            else{
                var panelRect = [x, y - 30, 100, 30];
                this.drawPanelButton(panelRect, panelKey);

                if (!this.setCategoryFn[panelKey]){
                    this.setCategoryFn[panelKey] = () => {
                        this.currentCategory = panelKey;
                    };
                }
                var setCategoryFn = this.setCategoryFn[panelKey];
                panelButtons.push([panelRect, setCategoryFn]);
            }
        });

        btns = _.filter(btns, (button) => {
            return button.uiDataComponent.subPanel == this.currentCategory;
        });

        ctxRoundedRect([rect[0] + 5, y + 5, rect[2] - 10, 60 * btns.length + 20]);

        y += 50;

        _.each(btns, (button) => {
            ctx.font = getFont((button.identityComponent.name.length > 17) ? 14 : 18);

            ctx.drawImage(traitImage, rect[0] + 20, y - 38);
            ctx.drawImage(getImage(button.uiDataComponent.image), rect[0] + 20, y - 38);
            
            ctx.fillText(button.identityComponent.name, rect[0] + 80, y);

            ctx.font = '12px OpenSansItalic';
            if (_.isArray(button.descriptionComponent.description)){
                _.each(button.descriptionComponent.description, (desc, i) => {
                    ctx.fillText(desc, rect[0] + 260, y - 7 + 15 * i);
                });
            }
            else{
                ctx.fillText(button.descriptionComponent.description, rect[0] + 260, y - 2);
            }
            ctx.font = getFont(20);

            var x = rect[0] + rect[2] - 180;

            if (!_.contains(traitEntity.interactionComponent.connectionKeys, PARAMETERS.self)){
                var start = world.entityForKey(PARAMETERS.self).positionComponent.getAsPos();
                var end = traitEntity.positionComponent.getAsPos();
                if (determinePath(start, end, world.getAllPaths())){
                    traitEntity.interactionComponent.connectionKeys.push(PARAMETERS.self);
                }
            }

            if (button.interactionComponent.canInteractSelf() && _.contains(traitEntity.interactionComponent.connectionKeys, PARAMETERS.self)){
                var uiActions = button.uiActionComponent.getUiActions(button, ui, entity);

                _.each(uiActions, (fn, key) => {
                    buttonRect = [x, y - 30, 80, 35];
                    this.drawPanelButton(buttonRect, key);
                    panelButtons.push([buttonRect, fn]);
                    
                    x += 90;
                });

                if (!uiActions['Build']){
                    ctx.font = getFont(14);
                    ctx.fillText('Limit', x, y - 15);
                    ctx.fillText('Reached', x, y);
                    ctx.font = getFont(20);
                }
            }
            else{
                ctx.fillText('No Control', x, y - 10);
            }
                
            y += 60;
        });
    });

    ctx.font = BASE_FONT;

    y = rect[1] + rect[3] - 100;

    if (lineCount > 6){
        if (this.page > 1){
            buttonRect = [rect[0] + 10, y, 100, 40];
            this.drawPanelButton(buttonRect, 'Page Up');
            panelButtons.push([buttonRect, this.pageUpFunction]);
        }

        if (this.page < maxPage){
            buttonRect = [rect[0] + rect[2] - 110, y, 100, 40];
            this.drawPanelButton(buttonRect, 'Page Down');
            panelButtons.push([buttonRect, this.pageDownFunction]);
        }
    }

    return panelButtons;
};


var allGoalPanelRender = function(rect, entity, ui){
    var panelButtons = [];
    var world = ui.world;

    var goals = _.filter(world.entities, function(worldEntity){
        return worldEntity.goalTrackComponent;
    });

    entity.absolutePositionComponent.size[1] = 200 + goals.length * 70;
    rect = entity.absolutePositionComponent.getAbsPosRect();

    ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);

    ctx.fillStyle = this.textColor;
    ctx.font = getFont(28);
    ctx.fillText('Goals', rect[0] + 60, rect[1] + 60);

    ctx.font = getFont(20);
    _.each(goals, (goal, i) => {
        var y = rect[1] + 100 + (i + 1) * 70;
        ctx.drawImage(getImage(goal.clickComponent.image), rect[0] + 10, y - 35);
        
        ctx.fillStyle = this.textColor;
        ctx.fillText(goal.identityComponent.name, rect[0] + 80, y);

        var x = rect[0] + rect[2] - 100
        _.each(goal.uiActionComponent.getUiActions(goal, ui, entity), (fn, key) => {
            var buttonRect = [x, y - 25, 85, 30];
            x -= 100;
            this.drawPanelButton(buttonRect, key);
            panelButtons.push([buttonRect, fn]);
        });

        ctx.strokeStyle = this.insetColor;
        ctxRoundedRect([rect[0] + 5, y - 40, rect[2] - 10, 60]);
    });

    return panelButtons;
};


var allTrainPanelRender = function(rect, entity, ui){
    var panelButtons = [];
    var world = ui.world;
    
    var trainPaths = _.filter(world.entities, function(worldEntity){
        return worldEntity.trainPathComponent && 
            worldEntity.trainPathComponent.stoppable &&
            worldEntity.interactionComponent.canInteractSelf();
    });

    rect = entity.absolutePositionComponent.getAbsPosRect();

    ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);

    ctx.fillStyle = this.textColor;
    ctx.font = getFont(28);
    ctx.textAlign = 'center';
    ctx.fillText('Trains of Thought', rect[0] + rect[2] / 2, rect[1] + 60);
    ctx.textAlign = 'left';

    ctx.font = getFont(20);
    ctx.fillStyle = this.textColor;
    ctx.strokeStyle = '#000000';

    for (var i = 0; i < 5; i++){
        var path = trainPaths[i];
        var y = rect[1] + 100 + (i + 1) * 80;
        
        if (path){
            var lastStop = world.entityForUid(_.last(path.trainPathComponent.trainStops));
            if (lastStop){
                lastStop.uiDataComponent.drawImage(lastStop, world, [rect[0] + 10, y - 36]);

                ctx.fillText(lastStop.identityComponent.name, rect[0] + 75, y);

                var buttonRect = [rect[0] + rect[2] - 60, y - 25, 40, 30];
                this.drawPanelButton(buttonRect, 'X');
                var cancelFunction = path.trainPathComponent.getKillTrainPath(path, world);
                panelButtons.push([buttonRect, cancelFunction]);
            }
        }
        else{
            ctx.textAlign = 'center';
            ctx.fillText('IDLE', rect[0] + rect[2] / 2, y);
        }

        ctxRoundedRect([rect[0] + 10, y - 40, rect[2] - 20, 60])
    };
    ctx.textAlign = 'left';

    return panelButtons;
};


var assertUnique = function(entity){
    console.log('Tried to make a duplicate of ' + entity.identityComponent.key);
    return false;
};


var autoPathGetReplaceKeys = function(entity, world){
    world.entityDeath();
    var paths = _.filter(world.entities, worldEntity => worldEntity.pathMoveComponent);
    var traits = _.filter(world.entities, worldEntity => worldEntity.mapSpawnComponent);
    for (var i = 0; i < traits.length; i++){
        console.log(i);
        var start = [traits[i].positionComponent.x, traits[i].positionComponent.y];
        for (var j = i + 1; j < traits.length; j++){
            console.log(j);
            var end = [traits[j].positionComponent.x, traits[j].positionComponent.y];
            world.ensurePath(start, end, paths);
        }
        break;
    }
    return false;
};


var autosaveOnGoalComplete = function(entity, args){
    var saveKey = 'save_' + args.key;
    adminConsole.save(saveKey);
};


var autosaveTick = function(entity, world){
    this.ticksToAutosave -= 1;
    if (this.ticksToAutosave > 0){
        return;
    }
    
    var chapter = _.filter(world.entities, function(worldEntity){
        return worldEntity.stageChangeComponent;
    })[0];

    if (chapter.identityComponent.key !== this.lastChapterKey){
        this.lastChapterKey = chapter.identityComponent.key;
        adminConsole.save(this.lastChapterKey);
    }

    adminConsole.save(PARAMETERS.autosaveKey);
    this.ticksToAutosave += (world.entities.length > 500) ? 3 : 1;
};


var checkEntityForBoost = function(entity){
    var that = this
    return entity.boostSocketComponent &&
        _.all(this.traits, function(trait){
            return _.contains(entity.boostSocketComponent.boostTraits, trait);
        });
};


var boostRender = function(rect, entity){
    ctx.drawImage(getImage('towerHex'), rect[0], rect[1]);
    ctx.fillStyle = '#ffff00';
    ctx.fillText(entity.identityComponent.name, rect[0] + rect[2] / 3, rect[1] + 2 * rect[3] / 3);
}


var makeExtraPathsExec = function(entity, world){
    world.makeExtraPaths([entity.positionComponent.x, entity.positionComponent.y]);
};


var buildingConnectExec = function(entity, world){
    if (PARAMETERS.buildingConnect){
        var stop;

        if (entity.identityComponent.sourceId){
            stop = world.entityForUid(entity.identityComponent.sourceId);
        }
        
        if (!stop && this.startEntityKey){
            stop = world.entityForKey(this.startEntityKey);
        }

        if (!stop || !stop.positionComponent){
            var baseKey = PARAMETERS.baseConnectKey ? PARAMETERS.baseConnectKey : PARAMETERS.self;
            stop = world.entityForKey(baseKey);
        }
        
        world.ensurePath(entity.positionComponent.getAsPos(), stop.positionComponent.getAsPos());
    }
};


var entityGenerateUiActions = function(entity, ui, panel){
    return {
        Inspect: function(){
            entity.clickComponent.onClick(entity, ui.world, ui);
            panel.panelComponent.closed = true;
        }
    };
};


var buildPanelRender = function(rect, entity, ui){
    entity.panelComponent.panelButtons = [];
    ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);

    var size = 40;
    var y = rect[1] + rect[3] / 2 - size / 2;
    
    if (PARAMETERS.buildablePaths){
        if (!this.pathButtonFunction){
            this.pathButtonFunction = function(){
                ui.world.togglePathBuilding();
            };
        }

        
        var pathRect = [rect[0] + 30, y, size, size];
        this.drawImageButton(pathRect, 'pathButton')
        entity.panelComponent.panelButtons.push([pathRect, this.pathButtonFunction]);
        
        if (!this.demolishPathButtonFunction){
            this.demolishPathButtonFunction = function(){
                if (!ui.world.destroyPathMode){
                    ui.world.resetCurrents();
                    ui.world.destroyPathMode = true;
                }
                else{
                    ui.world.resetCurrents();
                }
            };
        }

        var demolishPathRect = [rect[0] + 100, y, size, size];
        this.drawImageButton(demolishPathRect, 'noPath');
        entity.panelComponent.panelButtons.push([demolishPathRect, this.demolishPathButtonFunction]);
    }

    if (!this.demolishButtonFunction){
        this.demolishButtonFunction = function(){
            if (!ui.world.destructionMode){
                ui.world.resetCurrents();
                ui.world.destructionMode = true;
            }
            else{
                ui.world.resetCurrents();
            }
        };
    }
        
    var demolishRect = [rect[0] + rect[3] / 2 - size / 2 + 20, y, size, size];
    this.drawImageButton(demolishRect, 'bulldozer');
    entity.panelComponent.panelButtons.push([demolishRect, this.demolishButtonFunction]);

    if (!this.settingsFunction){
        this.settingsFunction = function(){
            ui.world.openSettingsPanel();
        };
    };

    this.font = getFont(10);
    var settingsRect = [rect[0] + rect[2] - 50, rect[1] + rect[3] - 20, 50, 20];
    this.drawPanelButton(settingsRect, 'Options');
    entity.panelComponent.panelButtons.push([settingsRect, this.settingsFunction]);
};


var bulletTick = function(entity){
    entity.healthComponent.health = 0;
};


var bulletRender = function(rect, entity, ui){
    var startRect = ui.world.hexMap.cellForPosComp(this.bulletSourcePos).rect;
    var endRect = ui.world.hexMap.cellForPosComp(this.bulletEndPos).rect;
    ctx.strokeStyle = '#ffffff';
    ctx.beginPath();
    ctx.moveTo(startRect[0] + startRect[2] / 2, startRect[1] + startRect[3] / 2);
    var endPos = [endRect[0] + endRect[2] / 2, endRect[1] + endRect[3] / 2];
    ctx.lineTo(endPos[0], endPos[1]);
    ctx.stroke();
};


var renderButton = function(text, buttonRect, buttonFunction){
    ctx.drawImage(getImage('button_l'), buttonRect[0] - 4, buttonRect[1] - 1);
    ctx.drawImage(getImage('button_c'), buttonRect[0], buttonRect[1] - 1, buttonRect[2], buttonRect[3] + 6);
    ctx.drawImage(getImage('button_r'), buttonRect[0] + buttonRect[2], buttonRect[1] - 1);
    
    ctx.fillStyle = '#000000';
    ctx.fillText(text, buttonRect[0], buttonRect[1] + this.lineHeight - 2);
    this.panelButtons.push([buttonRect, buttonFunction]);
};


var buttonGenerateUiActions = function(entity, ui, panel){
    var managerKey = entity.buttonControlComponent.managerKey;
    var manager = ui.world.entityForKey(managerKey);

    var actions = {};

    if (manager){
        if (this.inspectFunction === undefined){
            this.inspectFunction = function(){
                ui.minimizeAllPanels();
                manager.clickComponent.onClick(manager, ui.world, ui);
            };
        }
        actions['Inspect'] = this.inspectFunction;

        if (entity.buttonControlComponent.canMakeEntity(ui.world)){
            actions['Build'] = entity.buttonControlComponent.getWrappedFunction(entity, ui.world, ui);
        }
    }
    
    return actions;
};


var carryTrainTick = function(entity, world){
    var currenciesHeld = entity.trainComponent.getCurrenciesHeld(entity, world);
    if (currenciesHeld.length === 0){
        entity.healthComponent.kill();
    }
};


var changeOpinion = function(entity, world){
    if (this.opinionToRemove){
        var opinions = _.isArray(this.opinionToRemove) ? this.opinionToRemove : [this.opinionToRemove];
        _.chain(opinions)
            .map(function(opinionKey){ return world.entitiesForKey(opinionKey); })
            .filter(function(opinion){
                return opinion.personHeldComponent.personHeld == entity.personHeldComponent.personHeld
            })
            .each(function(opinion){
                opinion.healthComponent.health = 0;
            });
    }
    
    if (this.opinionToAdd){
        world.makeEntities(entity, this.opinionToAdd);
    }
};


var chapterPanelRender = function(rect, entity, ui){
    var panelButtons = [];
    var gameStage = ui.world.getGameStage();

    ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);

    ctx.fillStyle = this.textColor;
    ctx.font = (gameStage.identityComponent.name.length > 12) ? getFont(22) : getFont(28);
    drawCenteredText(gameStage.identityComponent.name, rect[0] + rect[2] / 2, rect[1] + 40);
    
    var x = rect[0] + 10;
    var y = rect[1] + 100;
    
    ctx.font = getFont(20);
    ctx.strokeStyle = '#000000';

    if (gameStage.dayResponseComponent.daysLeft > 0 && 
        Object.keys(gameStage.stageChangeComponent.goalsToCheck).length > 0){
        ctx.fillText('Days to go: ' + gameStage.dayResponseComponent.daysLeft, x, y);
        y += 40;
    }
    
    _.each(gameStage.stageChangeComponent.goalsToCheck, (chk, goalKey) => {
        if (allArgs[goalKey]){
            ctx.strokeRect(x, y - 14, 14, 14);

            if (chk <= 0){
                ctx.drawImage(getImage("tick"), x, y - 16);
            }
            else if (this.canShowGoal(goalKey, ui.world)){
                var buttonRect = [rect[0] + rect[2] - 110, y - 18, 100, 25];
                this.drawPanelButton(buttonRect, 'GO');
                
                if (!this.goalOpenFunctions[goalKey]){
                    this.goalOpenFunctions[goalKey] = (entity, ui) => {
                        this.showGoal(goalKey, ui.world, entity, ui);
                    };
                }

                panelButtons.push([buttonRect, this.goalOpenFunctions[goalKey]]);
            }

            txt = allArgs[goalKey].name;

            ctx.fillText(txt, x + 30, y);
            
            y += 30;
        }
    });
     
    return panelButtons;
};

var chapterPanelCanShowGoal = function(goalKey, world){
    return world.entityForKey(goalKey) ||
        _.any(world.entities, worldEntity => (worldEntity.buttonControlComponent && 
                                              worldEntity.buttonControlComponent.entityKey == goalKey));
};

var chapterPanelShowGoal = function(goalKey, world, entity, ui){
    entityPanelClose(entity, ui);
    
    var goal = world.entityForKey(goalKey);
    if (goal){
        goal.clickComponent.onClick(goal, world, ui);
    }
    else{
        var button = _.filter(world.entities, worldEntity => (worldEntity.buttonControlComponent && 
                                                              worldEntity.buttonControlComponent.entityKey == goalKey))[0];
        if (button){
            var manager = world.entityForKey(button.buttonControlComponent.managerKey);
            if (manager){
                manager.clickComponent.onClick(manager, world, ui);
            }
        }
    }
};


var checkManagedComponent = function(entity, world){
    if (entity.managedComponent){
        var manager = world.entityForKey(entity.managedComponent.managerKey);
        if (!manager){
            entity.healthComponent.kill();
        }
    }
};


var checkQualityTick = function(entity, world){
    var qualityEntity = world.entityForKey(this.qualityKey);
    if (qualityEntity && qualityEntity.qualityComponent.quality < this.minQuality){
        world.makeEntities(entity, this.minQualityEntities);
    }
};


var clearTraitExec = function(entity, world){
    _.each(world.entities, worldEntity => {
        if (worldEntity.managedComponent && worldEntity.managedComponent.managerKey == this.trait){
            worldEntity.healthComponent.kill();
        }
    });
};


var clickTextRender = function(rect, entity){
    ctx.fillStyle = '#ff00ff';
    ctx.fillText(entity.identityComponent.name, rect[0], rect[1] + rect[3] / 4);
};


var clickKill = function(entity){
    entity.healthComponent.health = 0;
};


var closestOpenPositionExec = function(entity, world){
    var checkedPositions = [];
    var positionsToCheck = [this.startPos];
    for (var j = 0; j < 10; j++){
        var nextPositions = [];
        for (var i = 0; i < positionsToCheck.length; i++){
            var checkPos = positionsToCheck[i];
            if (world.isPosBuildable(checkPos) && (
                checkPos[0] > 1 && 
                    checkPos[1] > 1 && 
                    checkPos[0] < PARAMETERS.size[0] - 1 &&
                    checkPos[1] < PARAMETERS.size[1] - 1
            )){
                entity.positionComponent.x = checkPos[0];
                entity.positionComponent.y = checkPos[1];
                return;
            }
            else{
                checkedPositions.push(checkPos);
                _.each(world.hexMap.neighbors(checkPos[0], checkPos[1]), function(neighbor){
                    if (!_.any(checkedPositions, function(checkedPos){ return comparePos(checkedPos, neighbor); })){
                        nextPositions.push(neighbor);
                    }
                });
            }
        }
        positionsToCheck = nextPositions;
    }
    
    if (entity.healthComponent){
        entity.healthComponent.kill();
    }
};


var conditionalMakeGetReplaceKeys = function(entity, world){
    if (_.any(this.stopEntities, function(stopEntityKey){ return world.entityForKey(stopEntityKey); })){
        return false;
    }
    return this.entitiesToMake;
};


var connectSourceGetReplaceKeys = function(entity, world){
    var source = world.entityForUid(this.sourceId);
    
    if (!source){
        return;
    }
    
    var source2 = world.entityForUid(source.identityComponent.sourceId);

    world.connectEntities(source, source2);
};


var construct = function(entity, world){
    world.makeEntities(entity, this.constructionIdentifier, {
        constructed: true
    });
    
    world.addEntity(makeEcs(PARAMETERS.notification, {
        notificationKey: PARAMETERS.goalComplete,
        notificationArgs: {
            key: entity.identityComponent.key
        }
    }));
};


var constructionRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    if (this.timeDrawn < 2){
        this.timeDrawn += lastInterval;
    }

    var img = getImage(this.image);
    
    if (this.timeDrawn < 0.05){
        return;
    }
    else if (this.timeDrawn < this.scaleTime){
        var scale = 1 + 0.5 * (this.scaleTime - this.timeDrawn) / this.scaleTime;
        scaledDraw(img, rect, scale);
    }
    else{
        var x = rect[0] + this.getWobble(lastInterval);
        ctx.drawImage(img, x, rect[1]);

        ctx.fillStyle = '#ffffff';
        ctx.fillRect(rect[0] + 17, rect[1] + 32, 26, 17);
        ctx.drawImage(getImage('construction'), rect[0] + 17, rect[1] + 32);
        
        if (this.outlineImage){
            ctx.drawImage(getImage(this.outlineImage), x, rect[1]);
            this.outlineImage = false;

            this.renderTrainIcon(lastInterval, x, rect[1]);
        }
    }

    if (this.timeDrawn > this.scaleTime && this.timeDrawn < this.scaleTime * 1.5){
        var dustImage = getImage(this.dustImage);
        var dist = ((this.timeDrawn - this.scaleTime) / (3 * this.scaleTime)) + 1;
        for (var i = 0; i < 5; i++){
            var angle = 2 * Math.PI * i / 5;
            var x = 60 * dist * Math.cos(angle);
            var y = 60 * dist * Math.sin(angle);
            ctx.drawImage(dustImage, rect[0] + rect[2] / 2 + x, rect[1] + rect[3] / 2 + y);
        }
    }
};


var constructionGetCurrencyComponent = function(entity){
    return entity.constructionComponent;
}


var constructOnClick = function(entity, world, ui){
    var uiKey = '#' + entity.identityComponent.key + '_' + entity.uid;
    ui.openPanelAtPos(uiKey, entity.positionComponent);
};


var constructionTick = function(entity, world){
    entity.constructionComponent.ticked = true;
};


var constructionMenuCloseExec = function(entity, world){
    if  (!entity.constructionComponent.ticked &&
         !_.any(world.entities, function(worldEntity){
             return worldEntity.trainPathComponent &&
                 _.contains(worldEntity.trainPathComponent.trainStops, entity.uid);
         })
        ){
        entity.healthComponent.kill();
    }
};


var getWrappedFunction = function(entity, world, ui){
    if (!this.wrappedFunction){
        this.wrappedFunction = function(){
            world.resetCurrents();
            if (entity.buttonControlComponent.canMakeEntity(world)){
                world.currentMakingEntity = entity;
                ui.minimizeAllPanels();
            }
        }
    }

    return this.wrappedFunction;
};

var controlComponentGetAiVal = function(world){
    return this.aiVal;
};


var controlPanelHandleKeyPress = function(key, entity, ui){
    if (key == 48 || key == 32 || key == 112){
        ui.pause();
    }
    else if (key == 81){
        entity.panelTrackComponent.closeTrackedPanel(ui);
    }
    else if (key == 80 && PARAMETERS.buildablePaths){
        ui.world.togglePathBuilding();
    }
    else if (key == 27){
        if (!entity.panelTrackComponent.closeTrackedPanel(ui)){
            ui.world.openSettingsPanel();
        }
    }
    else if (key > 48 && (key - 48 <= PARAMETERS.ticksPerTick.length)){
        ui.setSpeed(PARAMETERS.ticksPerTick[key - 49]);
    }

    var panelDict = {
    };
    var panelKey = panelDict[key];
    if (panelKey !== undefined){
        var infoPanel = ui.getPanel(panelKey);
        flipPanel(infoPanel, ui);
    }
};


var trackPanels = function(entities){
    var that = this;
    var panelKeysToRemove = [];
    var panelKeysToAdd = [];

    _.chain(entities)
        .filter(function(entity){
            return entity.panelComponent && entity.panelComponent.canClose;
        })
        .each(function(entity){
            var key = entity.identityComponent.key;
            if (entity.panelComponent.closed && _.contains(that.panelsOpened, key)){
                panelKeysToRemove.push(key);
            }
            else if (!entity.panelComponent.closed && !_.contains(that.panelsOpened, key)){
                panelKeysToAdd.push(key);
            }
        });
    
    this.panelsOpened = _.difference(this.panelsOpened, panelKeysToRemove).concat(panelKeysToAdd);
};

var closeTrackedPanel = function(ui){
    var world = ui.world;
    if (this.panelsOpened.length > 0){
        var panel = world.entityForKey(_.last(this.panelsOpened));
        if (panel){
            entityPanelClose(panel, ui);
        }
        this.panelsOpened.splice(this.panelsOpened.length - 1, 1);
        return true;
    }
    return false;
};

var closeAllPanels = function(ui){
    var world = ui.world;
    _.each(this.panelsOpened, function(panel){
        var panelToClose = world.entityForKey(panel);
        if (panelToClose){
            entityPanelClose(panelToClose, ui);
        }
    });
    this.panelsOpened = [];
};


var creepTick = function(entity, world){
    _.each(entity.rendererComponents, function(renderComponent){
        if (renderComponent.activated){
            renderComponent.activated -= 1;
        }
    });
};


var creepCreateExec = function(entity, world){
    if (this.makeTrain){
        var source = world.entityForUid(this.sourceId);
        var source2 = world.entityForKey(source.managedComponent.managerKey);

        if (!source2){
            entity.healthComponent.kill();
            return;
        }

        var self = world.entityForKey(this.destination);

        if (this.firstTick){
            var paths = world.getAllPaths();
            world.ensurePath([source2.positionComponent.x, source2.positionComponent.y],
                             [entity.positionComponent.x, entity.positionComponent.y],
                             paths);
            
            world.ensurePath([source2.positionComponent.x, source2.positionComponent.y],
                             [self.positionComponent.x, self.positionComponent.y],
                             paths);
        }
                
        var trainPath = makeEcs(this.creepTrainPath, {
            trainStops: [self.uid],
            trainStartId: entity.uid,
            focusCost: 0
        });
        world.addEntity(trainPath);
    }
};


var creepDeathExec = function(entity, world){
    var creepDeath = makeEcs(this.creepDeathKey, {
        renderPos: entity.rendererComponents[0].renderPos,
        image: entity.rendererComponents[0].image
    });
    world.addEntity(creepDeath);
};


var creepDeathRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    if (this.timeLeft < 0.25 || this.renderPos === undefined){
        entity.healthComponent.kill();
    }
    else{
        var width = this.timeLeft * this.size[0];
        var height = this.timeLeft * this.size[1];
        ctx.drawImage(getImage(this.image), 
                      this.renderPos[0] + this.size[0] / 2 - width / 2,
                      this.renderPos[1] + this.size[1] / 2 - height / 2,
                      width, height);
        this.timeLeft -= lastInterval;
    }
};


var getImmediateEmotionRect = function(rect, entity, rectCount){
    var size = 20;
    var x = rect[0] + rect[2] / 2 - size / 2;
    var y = rect[1] + rect[3] / 2 - size / 2;
    

    return [x, y, size, size];
};

var getEmotionRect = function(rect, entity, rectCount, ui){
    var emotionRect = getImmediateEmotionRect(rect, entity, rectCount);
    
    var ticks = ui.world.game.tickSystemWrapper.ticks;

    if (entity.pathVelocityComponent.isMoving){
        var fraction;
        var movingIndex = Math.floor(ticks / 5);
        if (entity.pathVelocityComponent.moveIndex < movingIndex){
            fraction = 1;
        }
        else if (entity.pathVelocityComponent.moveIndex > movingIndex){
            fraction = 0;
        }
        else{
            ticks = ticks % 5;
            fraction = ticks / 5;
        }
        
        var nextPos = entity.pathVelocityComponent.chosenPath[0];
        var nextRect = ui.world.hexMap.cellForPosComp({ x: nextPos[0], y: nextPos[1]}).rect;
        
        var interpolatedPos = [
            rect[0] + (nextRect[0] - rect[0]) * fraction + (rect[2] / 2) - emotionRect[2] / 2,
            rect[1] + (nextRect[1] - rect[1]) * fraction + (rect[3] / 2) - emotionRect[3] / 2,
        ];
        return [interpolatedPos[0], interpolatedPos[1], emotionRect[2], emotionRect[3]];
    }

    return emotionRect;
};

var currencyRender = function(rect, entity, ui, rectCount){
    var renderPos = this.renderPos;
    if (renderPos){
        ctx.drawImage(getImage(this.image), renderPos[0], renderPos[1]);
        ctx.fillStyle = '#ffffff';
        ctx.fillText(entity.identityComponent.name, renderPos[0], renderPos[1]);
    }
    else{
        var currencyRenderEntity = ui.world.entityForKey(PARAMETERS.currencyRenderEntity);
        currencyRenderEntity.currencyRenderEntityComponent.addCurrencyToRender([entity.positionComponent.x, 
                                                                                entity.positionComponent.y],
                                                                               this.image,
                                                                               entity.identityComponent.name);
    }
};


var currencyDayEnd = function(entity, world){
    var heldIds = _.chain(world.entities)
        .filter(function(worldEntity){
            return worldEntity.carriageComponent;
        })
        .map(function(carriage){
            return carriage.carriageComponent.holding;
        })
        .flatten(true)
        .value();

    if (!_.contains(heldIds, entity.uid)){
        entity.healthComponent.kill();
    }
};


var addCurrencyToRender = function(pos, image, name){
    var record = _.chain(this.currenciesToRender)
        .filter(function(chkRecord){
            return chkRecord.x == pos[0] &&
                chkRecord.y == pos[1] &&
                chkRecord.image == image &&
                chkRecord.name == name;
        })
        .first()
        .value();

    if (record){
        record.count += 1;
    }
    else{
        this.currenciesToRender.push({
            x: pos[0],
            y: pos[1], 
            image: image, 
            name: name, 
            count: 1});
    }
};


var allCurrencyRenderClear = function(rect, entity){
    entity.currencyRenderEntityComponent.currenciesToRender = [];
};


var allCurrencyRender = function(rect, entity, ui){
    var size = 20;
    ctx.fillStyle = '#ffffff';
    _.each(entity.currencyRenderEntityComponent.currenciesToRender, function(record){
        var renderRect = ui.world.hexMap.cellForPosComp(record).rect;
        var x = renderRect[0] + renderRect[2] / 2 - size / 2;
        var y = renderRect[1] + renderRect[3] / 2 - size / 2 + 5;

        for (var i = 0; i < record.count; i++){
            ctx.drawImage(getImage(record.image), x, y + i * 4);
        }
        var txt = record.name;
        ctx.fillText(txt, x, y);
    });
};


var currencyRequestedCurrenciesNeededAtPoint = function(entity){
    var currencyComponent = this.getCurrencyComponent(entity);
    return _.chain(currencyComponent.currencyRequested)
        .keys()
        .filter(function(key){
            return currencyComponent.currencyRequested[key] > 0;
        })
        .value();
};

var currencyRequestedInteractWithTrain = function(entity, world, train){
    var carriage = train.trainComponent.getCurrentCarriage(train, world);
    var didInteract = false;
    var currencyComponent = this.getCurrencyComponent(entity);

    _.chain(carriage.carriageComponent.holding)
        .map(holdingId => world.entityForUid(holdingId))
        .filter(holding => holding && holding.identityComponent)
        .each(function(holding){
            didInteract = true;
            var holdingKey = holding.identityComponent.key;
            holding.healthComponent.kill();
            if (currencyComponent.currencyRequested[holdingKey] > 0){
                currencyComponent.currencyRequested[holdingKey] -= 1;
            }
        });
    
    if (didInteract){
        if (this.currencyEntities){
            world.makeEntities(entity, this.currencyEntities);
        }

        if (this.currencyEffect){
            var rect =  world.hexMap.cellForPosComp(entity.positionComponent).rect;
            world.makeEntities(entity, this.currencyEffect, {
                effectPos: [rect[0] + rect[2] / 2, rect[1] + rect[3] / 2]
            });
        }
    }

    if (_.all(currencyComponent.currencyRequested, function(val){ return val == 0; })){
        var trainPath = world.entityForUid(train.trainVelocityComponent.trainPath);
        
        if (trainPath){
            if (entity.uiDataComponent){
                var stops = _.map(trainPath.trainPathComponent.trainStops, function(stopId){
                    return world.entityForUid(stopId);
                });

                if (_.all(stops, _.identity)){
                    var path = _.map(stops, function(stop){
                        var traitImage = stop.managedComponent ? 
                            traitImageForKey(stop.managedComponent.managerKey, world) : 
                            undefined;
                        return {
                            name: stop.identityComponent.name,
                            traitImage: traitImage,
                            image: stop.uiDataComponent.image
                        };
                    });
                    
                    entity.uiDataComponent.lastPath = path;
                }
            }

            trainPath.healthComponent.kill();
        }
        train.healthComponent.kill();
    }
    
    return false;
};

var currencyRequestedFocusPathStopPercentComplete = function(entity){
    var comp = this.getCurrencyComponent(entity);
    var currencyLeft = _.reduce(comp.currencyRequested, addFunction, 0);
    var allCurrency = _.reduce(comp.origCurrencyRequested, addFunction, 0);
    return Math.round((1 - (currencyLeft / allCurrency)) * 100);
};


var cursorRender = function(rect, entity, ui){
    var mousePos = ui.game.lastPos;
    var image;
    var button = ui.world.currentMakingEntity;
    if (button){
        var trait = ui.world.entityForKey(button.buttonControlComponent.managerKey);
        if (trait){
            ctx.drawImage(getImage(trait.uiDataComponent.image), mousePos[0] - 30, mousePos[1] - 26);
        }
        image = getImage(button.uiDataComponent.image);
        ctx.drawImage(image, mousePos[0] - 30, mousePos[1] - 26);
    }
    else if (ui.world.currentPath){
        ctx.strokeStyle = '#0099ff';
        ctx.beginPath();
        ctx.moveTo(mousePos[0], mousePos[1] - 10);
        ctx.lineTo(mousePos[0], mousePos[1] + 10);
        ctx.stroke();
    }
    else if (ui.world.destructionMode || ui.world.destroyPathMode){
        image = getImage('demolishIcon');
        ctx.drawImage(image, mousePos[0] - 16, mousePos[1] - 16);
    }
};


var dailyEntityMake = function(entity, world){
    var comp = entity.dailyEntityMakerComponent;
    comp.dayCount -= 1;
    if (comp.dayCount <= 0){
        var neighbors = world.hexMap.neighbors(entity.positionComponent.x, entity.positionComponent.y);
        var pos = _.chain(neighbors)
            .filter(function(neighbor){
                return world.isPosBuildable(neighbor);
            })
            .sample()
            .value();

        if (!pos){
            return;
        }

        world.makeEntities(entity, comp.dayStartEntity, {
            x: pos[0],
            y: pos[1]
        });
        
        comp.dayCount = comp.daysToMake;
    }
};


var getAllEmotionParts = function(){
    return [this.emotion].concat(this.traits);
}


var checkTargetEntity = function(targetEntity, entity, world){
    var entityCheck = (targetEntity !== entity &&
                       targetEntity.identityComponent !== undefined &&
                       targetEntity.positionComponent !== undefined &&
                       targetEntity.healthComponent !== undefined &&
                       targetEntity.healthComponent.health > 0 &&
                       targetEntity.damageableComponent !== undefined);
    if (!entityCheck){
        return false;
    }
    
    var damage = this.calculateDamage(targetEntity);
    if (damage === 0){
        return false;
    }

    var targetPos = [targetEntity.positionComponent.x, targetEntity.positionComponent.y];

    var hitPos = this.getHitPos(entity, world);

    return _.any(hitPos, function(neighbor){ return _.isEqual(neighbor, targetPos); });
};

var getHitPos = function(entity, world){
    if (!this.hitPos){
        var neighbors = world.hexMap.neighbors(entity.positionComponent.x, entity.positionComponent.y);
        this.hitPos = neighbors.concat([entity.positionComponent.x, entity.positionComponent.y]);
    }
    return this.hitPos;
};

var calculateDamage = function(targetEntity){
    var relevantTraits = _.filter(targetEntity.damageableComponent.getAll(), (key) => { 
        return this.damageMultipliers[key] !== undefined; 
    });
    
    if (relevantTraits.length === 0){
        return 0;
    }

    var multiplier = _.chain(relevantTraits)
            .map((key) => { return this.damageMultipliers[key]; })
            .reduce(addFunction, 0)
            .value();

    return this.damage * multiplier;
};


var damageIndicatorRender = function(rect, entity, ui){
    var source = ui.world.entityForUid(self.sourceId);
    
    if (source){
        rect = ui.world.hexMap.cellForPosComp(source.positionComponent).rect;
        ctx.fillStyle = '#000000';
        ctx.fillRect(rect[0] + 20, rect[1] + 20, 20, 20);
    }
};


var damamgeUpgradeCheckExec = function(entity, world){
    var existingUpgrades =_.filter(world.entities, (worldEntity) => {
        return worldEntity.damageUpgradeComponent &&
            _.contains(worldEntity.damageUpgradeComponent.towerKeys, this.towerKey);
    });
    
    if (existingUpgrades.length >= this.maxUpgradeCount - 1){
        _.each(this.entitiesToKill, function(entityKey){
            _.each(world.entitiesForKey(entityKey), function(worldEntity){
                worldEntity.healthComponent.kill();
            });
        });

        world.flags[this.flag] = true;
    }
};


var getPartOfDay = function(){
    var partsOfDay = ['early morning', 'late morning', 'afternoon', 'evening', 'night'];
    var index = Math.floor((this.impulseTicks - this.ticks) / (this.impulseTicks / partsOfDay.length));
    return partsOfDay[index];
};


var dayKillExec = function(entity, world){
    world.makeEntities(entity, PARAMETERS.day, {
        dayCount: entity.dayCounterComponent.dayCount + 1
    });
    adminConsole.save('day');
};


var drawBar = function(rect, fraction){
    ctx.fillStyle = '#000000';
    ctxFillRect(rect);

    rect[2] *= fraction;
    ctx.fillStyle = '#ffffff';
    ctxFillRect(rect);
};

var dayPanelRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    entity.panelComponent.panelButtons = [];
    ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);
    ctx.fillStyle = this.textColor;
        
    if (ui.world.isEntityPaused()){
        var pausingEntity = findPausingEntity(ui);
        this.flashTimer += lastInterval;
        if (this.flashTimer < 1.8){
            if (pausingEntity){
                var showPauseRect = [rect[0] + rect[2] / 2 - 100, rect[1] + rect[3] / 2 - 25, 200, 50];
                this.textColor = '#ffd700';
                this.insetColor = '#ffd700'
                this.drawPanelButton(showPauseRect, 'SHOW NEW GOAL');
                this.textColor = '#000000';
                this.insetColor = '#000000';
                entity.panelComponent.panelButtons.push([showPauseRect, this.showPausingEntity]);
            }
            else{
                ctx.font = getFont(30);
                drawCenteredText('PAUSED', rect[0] + rect[2] / 2, rect[1] + rect[3] / 2 + 15);
                ctx.font = BASE_FONT;
            }
        }
        else if (this.flashTimer > 2){
            this.flashTimer -= 2;
        }
    }
    else{
        var gameStage = ui.world.getGameStage();
                
        if (gameStage){
            if (this.displayGameStage === undefined){
                this.displayGameStage = function(){
                    var chapterMenu = ui.world.entityForKey(PARAMETERS.chapterPanel);
                    if (!chapterMenu){
                        chapterMenu = makeEcs(PARAMETERS.chapterPanel);
                        ui.world.addEntity(chapterMenu);
                    }
                    chapterMenu.panelComponent.expandPanel();
                };
            }

            var buttonRect = [rect[0] + rect[2] / 2 - 95, rect[1] + 10, 190, 30];
            this.drawPanelButton(buttonRect, gameStage.identityComponent.name);
            entity.panelComponent.panelButtons.push([buttonRect, this.displayGameStage]);
        }

        var day = _.chain(ui.world.entities)
            .filter(function(worldEntity){
                return worldEntity.dayCounterComponent;
            })
            .first()
            .value();

        if (day){
            ctx.fillText('Day ' + day.dayCounterComponent.dayCount, rect[0] + rect[2] / 2 - 95, rect[1] + rect[3] - 30);
            drawBar([rect[0] + rect[2] / 2 - 47, rect[1] + rect[3] - 38, 135, 5], 
                    1 - (day.dayCounterComponent.ticks / day.dayCounterComponent.impulseTicks));

            var dayActions;
            if (this.dayActions){
                dayActions = this.dayActions;
            }
            else{
                dayActions = [['[P]ause', ui.pause]];

                _.each(PARAMETERS.ticksPerTick, function(ticksPerTick){
                    txt = '[' + Math.floor(ticksPerTick / PARAMETERS.ticksPerTick[0]) + ']x';
                    dayActions.push([txt, function(){ ui.setSpeed(ticksPerTick); }]);
                });
                this.dayActions = dayActions;
            }

            var ticksPerTick = PARAMETERS.ticksPerTick;
            var currentIdx = ui.game.tickSystemWrapper.running ? 
                (ticksPerTick.indexOf(ui.game.ticksPerTick) + 1) : 
                0;

            _.each(dayActions, (data, i) => {
                var text = data[0];
                var fn =  data[1];
                var actionRect = [rect[0] + rect[2] / 2 - 87 + 60 * i, rect[1] + rect[3] - 22, 55, 20];
                this.drawPanelButton(actionRect, text);

                if (currentIdx == i){
                    ctx.strokeStyle = '#ff0000';
                    ctx.strokeRect(actionRect[0], actionRect[1], actionRect[2], actionRect[3]);
                }

                entity.panelComponent.panelButtons.push([actionRect, fn]);
            });

            
        }

        var pointCounter = _.filter(ui.world.entities, function(worldEntity){
            return worldEntity.pointCounterComponent;
        })[0];
    }
};

var showPausingEntity = function(entity, ui){
    var pausingEntity = findPausingEntity(ui);
    if (pausingEntity){
        pausingEntity.clickComponent.onClick(pausingEntity, ui.world, ui);
    }
};

var findPausingEntity = function(ui){
    return _.chain(ui.world.entities)
        .filter((worldEntity) => worldEntity.pauseComponent && worldEntity.pauseComponent.paused && worldEntity.clickComponent)
        .first()
        .value();
};


var demolishButtonRender = function(rect, entity, ui){
    if (!this.demolishButtonFunction){
        this.demolishButtonFunction = function(){
            if (!ui.world.destructionMode){
                ui.world.resetCurrents();
                ui.world.destructionMode = true;
            }
            else{
                ui.world.resetCurrents();
            }
        };
    }
    ui.informLine(PARAMETERS.buildPanel, {
        buttonFunction: this.demolishButtonFunction,
        text: 'demolish'
    }, PARAMETERS.baseButtonName);
};


var demolishPathButtonRender = function(rect, entity, ui){
    if (!this.demolishPathButtonFunction){
        this.demolishPathButtonFunction = function(){
            if (!ui.world.destroyPathMode){
                ui.world.resetCurrents();
                ui.world.destroyPathMode = true;
            }
            else{
                ui.world.resetCurrents();
            }
        };
    }

    ui.informLine(PARAMETERS.buildPanel, {
        buttonFunction: this.demolishPathButtonFunction,
        text: 'remove path'
    }, PARAMETERS.baseButtonName);
};


var depositInteractWithTrain = function(entity, world, train){
    var trainRequirements = train.trainComponent.calculateTrainCurrencies(train, world);

    var deposit = entity.depositComponent.deposit;
    
    for (var key in deposit){
        if (deposit[key] > 0 && trainRequirements[key] > 0){
            var currency = world.makeEntities(entity, key)[0];
            var carriage = train.trainComponent.getCurrentCarriage(train, world);
            carriage.carriageComponent.holding.push(currency.uid);

            deposit[key] -= 1;
        }
    }
    
    return false;
};

var depositCurrenciesMadeAtPoint = function(entity){
    return _.chain(entity.depositComponent.deposit)
        .map(function(val, key){
            return (val > 0) ? key : false;
        })
        .filter(_.identity)
        .value();
};


var depositTick = function(entity, world){
    if (_.all(entity.depositComponent.deposit, function(val){ return val == 0; })){
        entity.healthComponent.health = 0;
    }
};


var checkMakeDeposit = function(entity, world){
    if (this.checkMakeDeposit){
        var existingDeposit = _.chain(world.entities)
            .filter(function(worldEntity){
                return worldEntity.depositComponent &&
                    _.all(entity.depositComponent.deposit, function(val, key){
                        return worldEntity.depositComponent.deposit[key];
                    }) &&
                    ((!entity.managedComponent && !worldEntity.managedComponent) ||
                     (entity.managedComponent && worldEntity.managedComponent &&
                      entity.managedComponent.managerKey == worldEntity.managedComponent.managerKey))
            })
            .first()
            .value();
            
        if (existingDeposit){
            addDict(existingDeposit.depositComponent.deposit, entity.depositComponent.deposit);
            entity.healthComponent.kill();
            if (existingDeposit.timedDeathComponent && entity.timedDeathComponent){
                existingDeposit.timedDeathComponent.timeToDeath = entity.timedDeathComponent.timeToDeath;
            }
        }
    }
};


var depositPositionExec = function(entity, world){
    if (this.randomPosition ||
        entity.positionComponent.x === undefined ||
        entity.positionComponent.y === undefined ||
        entity.managedComponent === undefined){
        randomOpenPositionExec(entity, world);
    }
}


var disabledTowerRender = function(rect, entity){
    ctx.drawImage(getImage(this.image), rect[0], rect[1]);
    ctx.fillStyle = '#ffffff';
    ctx.fillText(entity.identityComponent.name, rect[0] + rect[2] / 2, rect[1] + 3 * rect[3] / 4);
};


var checkMemoriesDisabled = function(trait){
    return trait && trait.memoryComponent;
};


var disableMemoriesTick = function(entity, world){
    var timeLeft = Math.floor(entity.timedDeathComponent.timeToDeath / PARAMETERS.ticksPerTick[0]);
    entity.identityComponent.name = this.name + ' (' + timeLeft + ')';
};


var checkTraitDisabled = function(trait){
    return trait && _.contains(this.traitsToDisable, trait.identityComponent.key);
};


var emotionResourceDifficultyExec = function(entity, world){
    var difficulty = world.entityForKey(PARAMETERS.difficultySetting).difficultySettingComponent.difficulty;
    entity.resourceComponent.resourceCount = max(entity.resourceComponent.resourceCount - difficulty, 1);
};


var emotionSourceCreateExec = function(entity, world){
    var spawnSource = world.entityForKey(this.sourceKey);

    if (!spawnSource){
        console.log('Failed to make emotion source because source key missing - ' + this.sourceKey);
        return;
    }

    if (!spawnSource.positionComponent){
        console.log(this.sourceKey + ' is missing a position component, so cannot spawn emotion source.')
    }
    
    var memory = world.entityForKey(this.memoryKey);
    var paths = world.getAllPaths();

    if (memory){
        if (memory.entityManagerComponent.disabled){
            return;
        }
        
        memory.healthComponent.health += 100;

        var space = memory.entityManagerComponent.findFreeManagedSpace(memory, world);

        if (space){
            world.makeEntities(memory, this.resourceKey, {
                x: space.positionComponent.x,
                y: space.positionComponent.y,
                sourceTrait: this.memoryKey
            });

            world.ensurePath([memory.positionComponent.x, memory.positionComponent.y],
                             [space.positionComponent.x, space.positionComponent.y],
                             paths);
        }
    }
    else{
        if (_.any(world.entities, worldEntity => (worldEntity.disableTraitComponent && 
                                                  worldEntity.disableTraitComponent.disableNewMemories))){
            return;
        }
        
        var emotion = allArgs[this.memoryKey].memoryEmotions[0];
        
        var startManagedEntities = _.chain(world.entities)
            .filter(function(worldEntity){
                return worldEntity.emotionResourceMapComponent &&
                    worldEntity.emotionResourceMapComponent.emotionToMap == emotion;
            })
            .map(function(worldEntity){
                return worldEntity.emotionResourceMapComponent.startManagedEntities;
            })
            .flatten(true)
            .value();
        startManagedEntities.push(this.resourceKey)

        memory = world.makeEntities(entity, this.memoryKey, {
            source2Key: this.sourceKey,
            sourceName: spawnSource.identityComponent.name,
            startManagedEntities: startManagedEntities,
            startPos: [spawnSource.positionComponent.x, spawnSource.positionComponent.y]
        })[0];

        var shouldKill = true;
        if (memory && memory.healthComponent.health > 0){
            shouldKill = world.ensurePath([memory.positionComponent.x, memory.positionComponent.y],
                                          [spawnSource.positionComponent.x, spawnSource.positionComponent.y],
                                          paths) === undefined;
        }
        
        if (shouldKill){
            entity.healthComponent.kill();
        }
    }
};


var emotionSourceConstructionCreateExec = function(entity, world){
    var memory = world.entityForKey(this.sourceKey);
    var source = world.entityForKey(memory.memoryComponent.source2Key);
    
    if (source){
        var focusStop = world.makeEntities(source, this.focusStopKey)[0];
        
        var trainPath = world.makeEntities(entity, this.trainPath, {
            trainStops: [entity.uid],
            trainStartId: focusStop.uid
        })[0];

        world.ensurePath([entity.positionComponent.x, entity.positionComponent.y],
                         [memory.positionComponent.x, memory.positionComponent.y],
                         world.getAllPaths());
    }
    else{
        entity.healthComponent.kill();
    }
};


var endGoalExecute = function(entity, world){
    world.flags[this.flag] = true;
    _.each(world.entitiesForKey(this.buttonKey), entity => entity.healthComponent.kill());
};


var makeLifecycleEntities = function(entity, world, entitiesToMake){
    this.entitiesMade = _.map(world.makeEntities(entity, entitiesToMake), function(entity){
        return entity.uid;
    });
};

var endLifecycle = function(world){
    _.each(this.entitiesMade, function(entityUid){
        var entity = world.entityForUid(entityUid);
        if (entity){
            entity.healthComponent.kill();
        }
    });
};


var canMakeManagedEntity = function(entity, world){
    return true;
};

var findFreeManagedSpace = function(entity, world){
    var key = entity.identityComponent.key;

    var managedEntities = _.filter(world.entities, function(worldEntity){
        return worldEntity.managedComponent &&
            worldEntity.managedComponent.managerKey == key;
    });

    var space = _.chain(world.entities)
        .filter(function(worldEntity){
            return (worldEntity.identityComponent && worldEntity.identityComponent.key == key) ||
                (worldEntity.sourceExtensionComponent && worldEntity.sourceExtensionComponent.sourceKey == key);
        })
        .filter(function(worldEntity){
            return !_.any(managedEntities, function(managedEntity){
                return managedEntity.positionComponent.comparePos(worldEntity.positionComponent);
            });
        })
        .sample()
        .value();
    
    return space;
};

var checkForNewManaged = function(entity, world){
    this.hasNew = _.any(world.entities, worldEntity => (worldEntity.buttonControlComponent &&
                                                        worldEntity.buttonControlComponent.isNew &&
                                                        worldEntity.buttonControlComponent.managerKey == entity.identityComponent.key));
};


var getSaneName = function(entityKey){
    if (allArgs[entityKey]){
        return allArgs[entityKey].name;
    }
    else{
        return entityKey;
    }
};

var sanitize = function(entityKeys){
    if (_.isArray(entityKeys)){
        return reduceArray(_.map(entityKeys, getSaneName));
    }
    else if (_.isObject(entityKeys)){
        return _.reduce(entityKeys, function(memo, val, key){
            var st = sanitize(key) + ' (' + val + ')';
            return memo ? (memo + ', ' + st) : st;
        }, false);
    }
    else{
        return getSaneName(entityKeys);
    }
};

var entityPanelRender = function(rect, entity, ui){
    var panelButtons = [];
    var world = ui.world;
    var building = world.entityForUid(entity.panelComponent.sourceId);

    if (!building){
        return;
    }

    ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);
    if (building.uiDataComponent){
        building.uiDataComponent.drawImage(building, world, [rect[0] + 5, rect[1] + 5]);
    }
    ctx.drawImage(getImage(this.entityImage), rect[0] + 5, rect[1] + 5);

    ctx.fillStyle = this.textColor;
    var titleLength = building.identityComponent.name.length;
    var titleFontSize = (titleLength > 20) ? 18 :
        (titleLength > 15) ? 24 :
        36;
    ctx.font = getFont(titleFontSize);
    var titleWidth = ctx.measureText(building.identityComponent.name).width;

    ctx.fillText(building.identityComponent.name, rect[0] + (rect[2] - titleWidth) / 2, rect[1] + 40);

    panelButtons.push(this.drawHelpButton(entity, rect));
    
    ctx.font = getFont(20);

    if (building.personHeldComponent && building.personHeldComponent.personHeld){
        ctx.fillText(sanitize(building.personHeldComponent.personHeld), rect[0] + 10, rect[1] + 75);
    }
    
    var currenciesNeeded, currenciesMade;
    
    if (building.focusPathStopComponent){
        currenciesNeeded = building.focusPathStopComponent.currenciesNeededAtPoint(building, world);
        currenciesMade = building.focusPathStopComponent.currenciesMadeAtPoint(building, world);
    }
    else{
        currenciesNeeded = [];
        currenciesMade = [];
    }

    var startY = rect[1] + 110;

    var description = (building.descriptionComponent && building.descriptionComponent.description.length > 0) ?
        building.descriptionComponent.description :
        undefined;

    if (description){
        ctx.font = '16px OpenSansItalic';

        if (_.isArray(description)){
            _.each(description, function(desc, i){
                ctx.fillText(desc, rect[0] + 35, startY);
                startY += 35;
            });
        }
        else{
            ctx.fillText(description, rect[0] + 35, startY);
            startY += 90;
        }

        ctx.font = getFont(20);
    }

    var lines = [];
    

    if (building.timedDeathComponent){
        lines.push('TIME LEFT: ' + Math.floor(building.timedDeathComponent.timeToDeath / PARAMETERS.ticksPerTick[0]));
    }

    if (currenciesMade.length > 0 && !building.emotionSourceComponent && !building.depositComponent){
        lines.push('MAKES:     ' + sanitize(currenciesMade));
    }
    
    if (building.resourceComponent && building.resourceComponent.resourceCount){
        lines.push('' + building.resourceComponent.resourceCount + ' left');
    }
    if (building.depositComponent){
        lines.push('Built by ' + building.identityComponent.sourceName);
        lines.push('HOLDS:     ' + sanitize(currenciesMade));
        var amountLeft = _.reduce(building.depositComponent.deposit, addFunction, 0);
        lines.push('' + amountLeft + ' left');
    }
    if (building.damageComponent){
        lines.push('DAMAGE DONE: ' + building.damageComponent.damage);
        var line = 'CHARGES HELD:  ' + building.damageComponent.charges + ' / ' + building.damageComponent.maxCharges;
        if (building.damageComponent.maxCharges > building.damageComponent.charges){
            line += '  (start train to recharge)'
        }
        lines.push(line);
    }
    if (building.emotionSourceComponent){
        lines.push('EMOTION LEFT:' + reduceArray(building.emotionSourceComponent.emotions));
        var sourceEntity = world.entityForUid(building.identityComponent.sourceId);
        if (sourceEntity){
            lines.push('SOURCE: ' + sourceEntity.identityComponent.name);
        }
    }
    if (building.focusPathStopComponent && building.focusPathStopComponent.currencyEntities){
        if (_.isArray(building.focusPathStopComponent.currencyEntities) ||
            _.isString(building.focusPathStopComponent.currencyEntities)){
            var colorText = {
                anger: '#660000',
                sadness: '#000066',
                fear: '#660066',
                joy: '#dddd00'
            };
            
            var st = sanitize(building.focusPathStopComponent.currencyEntities);
            var params = allArgs[building.focusPathStopComponent.currencyEntities];
            if (params && params.emotionsToAdd){
                var emotion = Object.keys(params.emotionsToAdd)[0];
                var color = colorText[emotion];
                emotion = emotion.charAt(0).toUpperCase() + emotion.slice(1);
                st = emotion + ' due to ' + st;
                lines.push({
                    text: 'MAKES: ' + st,
                    color: color
                })
            }
            else{
                lines.push('MAKES:  ' + st);
            }
        }
    }
    if (building.dailyEntityMakerComponent){
        var daysLeft = building.dailyEntityMakerComponent.dayCount;
        daysLeft = (daysLeft > 1) ? (daysLeft + ' DAYS') : (daysLeft + ' DAY')
        lines.push('MAKES: ' + sanitize(building.dailyEntityMakerComponent.dayStartEntity) + ' IN ' + daysLeft);
    }
    if (building.healthComponent && building.healthComponent.maxHealth > 1){
        lines.push('HEALTH: ' + building.healthComponent.health + '/' + building.healthComponent.maxHealth);
    }
    
    var i;
    
    for (i = 0; i < lines.length; i++){
        var line = lines[i];
        if (line.color){
            ctx.fillStyle = line.color;
            ctx.fillText(line.text, rect[0] + 35, startY);
            ctx.fillStyle = '#000000';
        }
        else{
            ctx.fillText(line, rect[0] + 35, startY);
        }
        startY += 40;
    }

    if (building.focusPathStopComponent && building.focusPathStopComponent.getCurrencyComponent){
        var comp = building.focusPathStopComponent.getCurrencyComponent(building);

        var xDiff = _.chain(comp.origCurrencyRequested)
            .map(function(val, key){
                return ctx.measureText(sanitize(key) + ':').width;
            })
            .max()
            .value() + 60;

        _.each(comp.origCurrencyRequested, function(val, key){
            if (val > 10){
                startY -= 10;
            }
            ctx.fillText(sanitize(key) + ':', rect[0] + 35, startY);
            for (var i = 0; i < val; i++){
                var img = (i < comp.currencyRequested[key]) ? 'currency' : 'filledCurrency';
                img = getImage(img);
                ctx.drawImage(img, rect[0] + xDiff + 25 * (i % 10), startY - 17 + Math.floor(i / 10) * 25);
            }
            startY += 40;
        });
    }

    if (building.flipComponent){
        var flipRect = [rect[0] + rect[2] / 2 - 100, startY + 20, 200, 30];
        var flipName = allArgs[building.flipComponent.flipEntity];
        this.drawPanelButton(flipRect, 'Flip to ' + flipName);
        panelButtons.push([rect, flipEntity]);
    }

    if (building.interactionComponent.canInteractSelf()){
        var paths = _.filter(world.entities, function(worldEntity){
            return worldEntity.trainPathComponent &&
                _.contains(worldEntity.trainPathComponent.trainStops, building.uid);
        });

        var pathRect = [rect[0] + rect[2] / 2 - 100, rect[1] + rect[3] - 100, 200, 30];

        if (paths.length > 0){
            var path = paths[0];
            if (path.trainPathComponent.canMakeTrain(path, world)){
                this.drawPanelButton(pathRect, 'SEE ACTIVE TRAIN');
                panelButtons.push([pathRect, entityPanelShowPath]);
            }
            else if (PARAMETERS.buildablePaths){
                this.drawPanelButton(pathRect, 'CONNECT TRACK');
                panelButtons.push([pathRect, entityPanelStartTrack]);
            }
        }
        else if (building.focusPathStopComponent && building.focusPathStopComponent.isPathEnd){
            if (!building.damageComponent || building.damageComponent.charges < building.damageComponent.maxCharges){
                var selfEntity = world.entityForKey(PARAMETERS.self);
                
                if (selfEntity.focusMakingComponent.focusUsing < selfEntity.focusMakingComponent.focusCount){
                    this.drawPanelButton(pathRect, 'SET UP TRAIN');
                    panelButtons.push([pathRect, entityPanelStartPath]);
                }
                else{
                    ctx.fillText('NO FOCUS LEFT', pathRect[0], pathRect[1] + pathRect[3]);
                }
            }
        }
    }
    else{
        ctx.fillText('You do not control this building.', rect[0] + rect[2] / 2 - 100, rect[1] + rect[3] - 70);
    }
 
    return panelButtons;
};

var flipEntity = function(entity, ui){
    var world = ui.world;
    var newEntity = world.makeEntities(entity, entity.flipComponent.flipEntity);
    entity.healthComponent.kill();

    if (entity.timedDeathComponent.timeToDeath){
        newEntity.timedDeathComponent.timeToDeath = entity.timedDeathComponent.timeToDeath;
    }
};

var entityPanelShowPath = function(entity, ui){
    var world = ui.world;
    var path = _.chain(world.entities)
        .filter(function(worldEntity){
            return worldEntity.trainPathComponent && 
                _.contains(worldEntity.trainPathComponent.trainStops, entity.panelComponent.sourceId);
        })
        .first()
        .value();
    
    if (path){
        var uiKey = 'trainPath_' + path.uid;
        var menu = world.entityForKey(uiKey);
        if (!menu){
            menu = makeEcs(PARAMETERS.trainPanel, {
                key: uiKey,
                sourceId: path.uid
            });
            world.addEntity(menu);
        }
        else{
            menu.panelComponent.expandPanel();
        }
    }
};

var entityPanelStartPath = function(entity, ui){
    var building = ui.world.entityForUid(entity.panelComponent.sourceId);
    ui.world.focusPathFromEntity(building);
    
    var menu = ui.world.entityForKey(PARAMETERS.trainPathMakingPanel);
    menu.panelComponent.expandPanel();

    entity.panelComponent.closed = true;
};

var entityPanelStartTrack = function(entity, ui){
    ui.world.togglePathBuilding();
};

var entityPanelTimeOut = function(entity, ui){
    var building = ui.world.entityForUid(entity.panelComponent.sourceId);
    building.timedDeathComponent.timeToDeath = 0;
};


var entityReplaceGetReplaceKeys = function(entity, world){
    if (this.skipFlag && world.flags[this.skipFlag]){
        return;
    }
    
    return this.entitiesToMake;
};


var getXpLevel = function(entity){
    var health = this.xpPerLevel;
    var i;
    for (i = 1; i < this.maxLevel; i++){
        health += i * this.xpPerLevel / 2;
        
        if (entity.healthComponent.health < health){
            break;
        }
    }
    return i;
};

var gainExp = function(entity, world){
    entity.healthComponent.health += 25;
    
    _.chain(world.entities)
        .filter(function(worldEntity){
            return worldEntity.experienceTriggerComponent &&
                worldEntity.experienceTriggerComponent.traitKey == entity.identityComponent.key;
        })
        .each(function(worldEntity){
            world.makeEntities(entity, worldEntity.experienceTriggerComponent.xpEntities);
        });
};

var updateExpansion = function(entity){
    var level = this.getXpLevel(entity);
    if (level > this.currentLevel){
        this.currentLevel = level;
    }
};


var fearTickModifyInteraction = function(entity, world, tower, train){
    if (this.fearFrozen > 0){
        this.fearFrozen -= 1;
    }
    else if (percentCheck(5)){
        this.fearFrozen = 3;
    }
    return this.fearFrozen <= 0;
};


var featureItemActionExec = function(entity, world){
    entity.currency[this.execCurrencyKey] += this.execCurrencyChange;
};


var flagBranchGetReplaceKeys = function(entity, world){
    return world.flags[this.flagToCheck] ? this.flagEntities : this.notFlagEntities;
};


var flaggedMakeGetReplaceKeys = function(entity, world){
    if (!world.flags[this.flagToCheck]){
        world.flags[this.flagToCheck] = true;
        return this.entitiesToMake;
    }
    return false;
};


var focusPanelRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    entity.panelComponent.panelButtons = [];
    ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);

    var selfEntity = ui.world.entityForKey(PARAMETERS.self);
    
    if (!this.toggleFocusPath){
        this.toggleFocusPath = function(){
            ui.world.toggleFocusPath();
            ui.expandPanel(PARAMETERS.trainPathMakingPanel);
        };

        this.showFocusPanel = function(){
            ui.expandPanel(PARAMETERS.allTrainPanel);
        };
    }

    ctx.fillStyle = this.textColor;


    var lostControl = ui.world.lostControl();
    var focusPanelRect = [rect[0] + rect[2] / 2 - 75, rect[1] + 10, 150, 30];

    if (lostControl){
        var textColor = this.textColor;
        this.textColor = '#cc0000';
        this.drawPanelButton(focusPanelRect, 'LOST CONTROL');
        
        
        var healthText = 'HEALTH: ' + selfEntity.healthComponent.health + '/' + selfEntity.healthComponent.maxHealth

        ctx.fillText(healthText, rect[0] + 30, rect[1] + 60);
        this.textColor = textColor;
    }
    else{
        var focusText = 'Trains used - ' + selfEntity.focusMakingComponent.focusUsing + ' / ' + selfEntity.focusMakingComponent.focusCount;
        
        if (selfEntity.focusMakingComponent.focusUsing === 0){
            this.insetColor = '#cc0000';
            this.textColor = this.insetColor;

            this.drawPanelButton(focusPanelRect, focusText);
            
            this.insetColor = '#000000';
            this.textColor = '#000000';
        }
        else{
            this.drawPanelButton(focusPanelRect, focusText);
        }
        
        entity.panelComponent.panelButtons.push([focusPanelRect, this.showFocusPanel]);

        var fraction = 1 - (selfEntity.focusTickerComponent.ticks / selfEntity.focusTickerComponent.impulseTicks);
        fraction = (fraction === 0) ? 1 : fraction;
        drawBar([focusPanelRect[0], focusPanelRect[1] + 40, focusPanelRect[2], 5], 
                fraction);

        ctx.fillStyle = this.textColor;
        var efficiency = Math.floor(100 * selfEntity.focusMakingComponent.focusEfficiency());
        ctx.fillText('(EFFICIENCY: ' + efficiency + '%)', focusPanelRect[0], focusPanelRect[1] + 70);
    }
    
};


var updateCurrencyDictWithStop = function(stop, world, currencyDict){
    _.each(this.currenciesMadeAtPoint(stop, world), function(currency){
        incrementDict(currencyDict, currency, -1);
    });

    _.each(this.currenciesNeededAtPoint(stop, world), function(currency){
        incrementDict(currencyDict, currency);
    });
};

var currenciesExistAtPoint = function(entity, world){
    var currenciesAtPoint = _.filter(world.entities, function(worldEntity){
        return worldEntity.pathVelocityComponent &&
            worldEntity.positionComponent.comparePos(entity.positionComponent);
    });

    return _.all(this.currenciesMadeAtPoint(entity, world), function(currency){ 
        return _.any(currenciesAtPoint, function(currencyAtPoint){
            return currencyAtPoint.identityComponent.key == currency;
        });
    });
};

var checkFocusStopDisabled = function(entity, world){
    var chkEnables = () => {
        return ((this.enableChecks.length > 0) &&
                _.all(this.enableChecks, function(chkKey){ return !world.entityForKey(chkKey); }));
    }; 
    var chkRequiredOpinions = () => {
        ((this.requiredOpinions.length > 0) &&
         entity.personHeldComponent &&
         _.all(this.requiredOpinions, function(opinion){
             return !_.any(world.entitiesForKey(opinion), function(opinionEntity){
                 return opinionEntity.personHeldComponent.personHeld == entity.personHeldComponent.personHeld;
             });
         })); 
    };
    var chkDisablingEntity = entity.managedComponent &&
        _.any(world.entities, function(worldEntity){
            return worldEntity.disableTraitComponent && worldEntity.disableTraitComponent.checkTraitDisabled(entity);
        });

    return chkDisablingEntity || chkEnables() || chkRequiredOpinions();
};


var focusRender = function(rect, entity, ui){
};


var aliveMakerFocusTick = function(entity, world){
    world.makeEntities(entity, this.aliveEntities);
};


var fullMenuOnClick = function(entity, world, ui){
    var uiKey = this.uiKey;
    var menu = world.entityForKey(uiKey);
    if (!menu){
        menu = makeEcs(this.panelKey, {
            key: uiKey,
            sourceId: entity.uid,
            entityImage: this.image,
            size: (entity.descriptionComponent && entity.descriptionComponent.description.length > 0) ? [550, 400] : [425, 300]
        });
        ui.world.addEntity(menu);
    }
    
    menu.panelComponent.expandPanel();
    
    var rect = world.hexMap.cellForPosComp(entity.positionComponent).rect;

    menu.absolutePositionComponent.x = rect[0] + rect[2];
    menu.absolutePositionComponent.y = rect[1] + rect[3];

    world.addEntity(makeEcs(PARAMETERS.notification, {
        notificationKey: PARAMETERS.openMenu,
        notificationArgs: {
            menuSourceKey: entity.uid
        }
    }));
};

var getUiKey = function(key, uid){
    return key + '_' + uid + '_menu';
};


var gainControlMenuRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    var animateBack = false;
    if (this.percentageComplete > 100){
        this.renderTime -= lastInterval;
        if (this.renderTime < 0){
            entity.healthComponent.kill();
            return;
        }
        animateBack = true;
    }
    else if (this.renderTime < this.fullAnimationTime){
        this.renderTime += lastInterval;
        animateBack = true;
    }

    if (animateBack){
        var fraction = this.renderTime / this.fullAnimationTime;
        ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2] * fraction, rect[3] * fraction);
        return;
    }

    ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);
    ctx.fillStyle = '#' + this.textColor;
    ctx.font = getFont(35);
    var txt = 'Regain Control';
    ctx.fillText(txt, rect[0] + (rect[2] - ctx.measureText(txt).width) / 2, rect[1] + 40);

    ctx.font = '16px OpenSansItalic';
    ctx.fillText(this.text, rect[0] + (rect[2] - ctx.measureText(this.text).width) / 2, rect[1] + 100);

    ctx.font = BASE_FONT;
    ctx.fillStyle = '#000000';
    txt = 'Keep your mind inside the bar to regain control.';
    ctx.fillText(txt, rect[0] + (rect[2] - ctx.measureText(txt).width) / 2, rect[1] + 140);
    
    ctx.fillStyle = '#' + this.barColor;
    ctx.fillRect(rect[0] + 10, rect[1] + this.barPos[1], rect[2] - 20, 40);
    
    this.barPos[0] = ui.game.lastPos[0] - (this.barWidth / 2) - rect[0];
    this.barPos[0] = clamp(this.barPos[0], rect[2] - 10 - this.barWidth, 10);

    ctx.fillStyle = '#dddddd';
    
    var controlRect = [rect[0] + this.barPos[0], rect[1] + this.barPos[1], this.barWidth, 40];
    ctxFillRect(controlRect);
    
    var brainRect = [rect[0] + this.brainPos[0], rect[1] + this.brainPos[1], 40, 35];
    ctx.drawImage(getImage(this.brainImage), brainRect[0], brainRect[1]);

    if (collideRect(controlRect, brainRect)){
        this.percentageComplete += 25 * lastInterval;
    }
    else{
        this.percentageComplete -= 20 * lastInterval;
        this.percentageComplete = max(this.percentageComplete, 0);
    }
    
    this.barWidth += 25 * lastInterval;
    this.barWidth = min(this.barWidth, rect[2] - 20);
    
    if (this.resetVelTime <= 0){
        this.brainVel = ((Math.random() * 4) - 2) * 60;
        this.resetVelTime = 1;
    }
    else{
        this.resetVelTime -= lastInterval;
    }
    
    this.brainPos[0] += this.brainVel * lastInterval;
    this.brainPos[0] = clamp(this.brainPos[0], rect[2] - 62, 10);

    if (this.percentageComplete < 100){
        ctx.fillStyle = '#ffffff';
        ctx.strokeStyle = '#ffffff';
        var lineWidth = ctx.lineWidth;
        ctx.lineWidth = 5;

        ctx.beginPath();
        var startAngle = Math.PI * -0.5;
        var endAngle = 2 * Math.PI * this.percentageComplete / 100 + startAngle;
        ctx.arc(rect[0] + rect[2] / 2, rect[1] + 300, 40, startAngle, endAngle);
        ctx.stroke();

        ctx.lineWidth = lineWidth;

        ctx.fillText('' + Math.floor(this.percentageComplete) + '%', rect[0] + rect[2] / 2 - 10, rect[1] + 300);
    }
};


var killLostControlExec = function(entity, world){
    _.chain(world.entities)
        .filter(worldEntity => worldEntity.holdControlComponent)
        .each((worldEntity) => { worldEntity.healthComponent.kill(); });
};


var gameEndRender = function(){
    ctx.fillStyle = '000000';
    ctx.fillRect(0, 0, width, height);
    ctx.font = getFont(40);
    ctx.fillStyle = '#ffffff';
    var txt = 'GAME OVER';
    var textWidth = ctx.measureText(txt).width;
    ctx.fillText(txt, (width - textWidth) / 2, height);
};


var checkStageComplete = function(entity, notificationArgs){
    if (entity.stageChangeComponent.goalsToCheck[notificationArgs.key]){
        entity.stageChangeComponent.goalsToCheck[notificationArgs.key] -= 1;
        entity.stageChangeComponent.shouldChange = _.all(entity.stageChangeComponent.goalsToCheck, function(val){
            return val <= 0;
        });
    }
};


var gameStageDayEnd = function(entity, world){
    world.trackActionables();
    this.daysLeft -= 1;
    if (this.daysLeft <= 0 && entity.stageChangeComponent.checkAndChange(entity, world)){
        entity.healthComponent.health = 0;
    }
    else{
        var dayEntity = _.filter(world.entities, function(worldEntity){
            return worldEntity.dayCounterComponent;
        })[0];
        
        var dayCount = dayEntity.dayCounterComponent.dayCount + 1;

        _.each(world.entities, function(worldEntity){
            if (worldEntity.panelComponent && worldEntity.panelComponent.canClose){
                worldEntity.panelComponent.closed = true;
            }
        });
    }
};


var checkAndChangeGameState = function(entity, world){
    if (this.shouldChange){
        _.each(this.executables, function(exec){
            exec.execute(entity, world);
        });
        return true;
    }
    return false;
};


var canMakeGoal = function(world){
    return world.entityForKey(this.entityKey) === undefined &&
        world.entityForKey(this.constructionKey) === undefined &&
        _.all(this.disablingEntities, function(disablingKey){ return world.entityForKey(disablingKey) === undefined; }) &&
        _.all(this.requiredEntities, function(requiredKey){ return world.entityForKey(requiredKey); });
};

var makeGoal = function(world, pos, entity, interactionKey){
    entity.buttonControlComponent.isNew = false;
    var manager = world.entityForKey(this.managerKey);
    if (manager){
        manager.entityManagerComponent.checkForNewManaged(manager, world);
    }


    var args = {
        x: pos[0],
        y: pos[1],
        sourceKey: this.managerKey,
        sourceTrait: this.managerKey
    };

    if (interactionKey){
        args.interactionKeys = [interactionKey];
    }

    if (entity.personHeldComponent){
        args.personHeld = entity.personHeldComponent.personHeld;
    }

    return makeEcs(this.constructionKey, args);
};


var goalCompleteExec = function(entity, world){
    world.addEntity(makeEcs(PARAMETERS.notification, {
        notificationKey: PARAMETERS.goalComplete,
        notificationArgs: {
            key: entity.identityComponent.key
        }
    }));
};


var goalCompleteMenuRender = function(rect, entity, ui){
    entity.panelComponent.panelButtons = [];

    if (entity.panelComponent.closed || entity.panelComponent.minimized){
        return;
    }

    this.snapPanelPos(entity);

    if (this.height !== undefined){
        rect[3] = this.height;
        entity.absolutePositionComponent.size[1] = this.height;
    }

    ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);
    
    var world = ui.world;
    
    var name = this.endGame ? 'GAME OVER' : entity.identityComponent.name;
    var completeText = this.completeText;

    var actionables = world.getActionables();

    var modifier = _.filter(actionables, function(actionable){ return actionable.menuModifierComponent; })[0];
    var randomMarker = _.filter(actionables, function(actionable){ return actionable.randomMenuComponent; })[0];
    actionables = _.filter(actionables, function(actionable){ return actionable.uiActionComponent; });
    
    if (modifier){
        name = modifier.menuModifierComponent.name ? modifier.menuModifierComponent.name : name;
        completeText = modifier.menuModifierComponent.completeText ? modifier.menuModifierComponent.completeText : completeText;
    }

    if (!(this.showPoints || completeText || actionables.length > 0)){
        entity.panelComponent.closed = true;
        return;
    }

    if (this.renderTime < this.titleTime){
        return;
    }

    ctx.fillStyle = this.textColor;

    if (this.sourceTrait){
        var traitImage = traitImageForKey(this.sourceTrait, world);
        
        if (traitImage){
            ctx.drawImage(getImage(traitImage), rect[0] + 5, rect[1] + 5);
        }
    }

    ctx.drawImage(getImage(this.image), rect[0] + 5, rect[1] + 5);
    ctx.font = (name.length > 25) ? getFont(20) : getFont(36);
    ctx.fillText(name, rect[0] + 85, rect[1] + 50);


    if (randomMarker){
        if (!this.carousel){
            this.carousel = makeEcs('randomCarousel');
            this.carousel.setMarker(randomMarker);
        }

        if (!this.carousel.animationDone){
            this.carousel.render([rect[0] + (rect[2] - 100) / 2, rect[1] + 200], this.renderTime - this.titleTime);

            if (this.carousel.animationDone){
                this.renderTime = this.titleTime;
            }
            return;
        }
    }

    if (this.renderTime < this.descriptionTime){
        return;
    }

    var y = rect[1] + 100;

    if (this.showingDescription){
        ctx.font = '20px OpenSansItalic';
        
        if (_.isArray(completeText)){
            _.each(completeText, function(line, i){
                ctx.fillText(line, rect[0] + 20, rect[1] + 120 + i * 25);
            });
            y += 20 * completeText.length;
        }
        else{
            ctx.fillText(completeText, rect[0] + 20, rect[1] + 120);
            y += completeText.length ? 25 : 0;
        }

        y = rect[1] + 200;

        var pathXOffset = 0;
        if (entity.goalCompleteMenuComponent.path && entity.goalCompleteMenuComponent.path.length > 0){
            ctx.fillText('Path Taken', rect[0] + 20, y);
            y += 50;
            pathXOffset = 200;
        }

        _.each(entity.goalCompleteMenuComponent.path, (pathPieces, i) => {
            var timePerRow = 0.25;
            var timeBeforeRender = this.descriptionTime + 0.25 * (1 + i);
            if (this.renderTime < timeBeforeRender){
                return;
            }
            var scale = (this.renderTime - timeBeforeRender) / 0.25;
            scale = min(scale, 1);

            if (pathPieces.traitImage){
                ctx.drawImage(getImage(pathPieces.traitImage), rect[0] + 20, y - 32, 60 * scale, 52 * scale);
            }
            ctx.drawImage(getImage(pathPieces.image), rect[0] + 20, y - 32, 60 * scale, 52 * scale);
            if (scale == 1){
                ctx.fillText(pathPieces.name, rect[0] + 80, y);
            }
            y += 60;
        });
        
        if (this.time < this.fullAnimationTime && completeText){
            return;
        }
    }
    else{
        ctx.font = getFont(20);

        var traits = _.chain(actionables)
            .filter(function(actionable){
                return actionable.entityManagerComponent;
            })
            .map(function(actionable){ return actionable.identityComponent.key; })
            .value();
        
        actionables = _.filter(actionables, function(actionable){
            return !(actionable.buttonControlComponent &&
                     _.contains(traits, actionable.buttonControlComponent.managerKey));
        });
        
        _.each(actionables, (entityMade) => {
            entityMade.uiDataComponent.drawImage(entityMade, world, [rect[0] + 15, y - 1]);
            
            ctx.fillText(entityMade.identityComponent.name, rect[0] + 90, y + 21);

            var x = rect[0] + rect[2] - 100;

            _.each(entityMade.uiActionComponent.getUiActions(entityMade, ui, entity), (fn, key) => {
                var rect = [x, y + 5, 85, 30];
                this.drawPanelButton(rect, key);
                entity.panelComponent.panelButtons.push([rect, fn]);

                x -= 100;
            });
            
            ctx.strokeStyle = this.insetColor;
            ctxRoundedRect([rect[0] + 5, y - 25, rect[2] - 10, 90]);
            
            y += 100;
        });
    }
    
    ctx.font = getFont(20);

    var closeRect = [rect[0] + rect[2] / 2 - 100, rect[1] + rect[3] - 50, 200, 40];

    if (entity.rebuildMenuComponent && this.showingDescription && !this.endGame){
        var rebuildFunction = entity.rebuildMenuComponent.getRebuildFunction(ui);
        if (rebuildFunction){
            var panelRect = [closeRect[0] + pathXOffset, closeRect[1] - 250, closeRect[2], 40];
            this.drawPanelButton(panelRect, 'REBUILD');
            entity.panelComponent.panelButtons.push([panelRect, rebuildFunction]);
            y += 50;
        }
    }
    
    if (actionables.length > 0){
        var flipRect;
        if (this.showingDescription){
            flipRect = [closeRect[0] + pathXOffset, closeRect[1] - 350, closeRect[2], closeRect[3] + 50];
            this.drawPanelButton(flipRect, 'SHOW UNLOCKS');
        }
        else{
            flipRect = [closeRect[0], closeRect[1] - 50, closeRect[2], closeRect[3]];
            this.drawPanelButton(flipRect, 'SHOW DESCRIPTION');
        }
        entity.panelComponent.panelButtons.push([flipRect, this.getFlipModeFunction()]);
    }


    this.drawPanelButton(closeRect, this.endGame ? 'END GAME' : 'CLOSE');
    
    if (!this.closeFunction){
        this.closeFunction = () => {
            entity.healthComponent.kill();
            entity.panelComponent.closed = true;
            if (this.endGame){
                window.location = 'select.html';
            }
        };
    }

    entity.panelComponent.panelButtons.push([closeRect, entityPanelClose]);
    
    ctx.font = BASE_FONT;
    return false;
};

var goalCompleteMenuFlipMode = function(){
    if (!this.flipModeFunction){
        var that = this;
        this.flipModeFunction = function(){
            that.showingDescription = !that.showingDescription;
        };
    }
    return this.flipModeFunction;
};


var goalCompleteMenuCreateExec = function(entity, world){
    var actionables = world.getActionables();
    if (!(this.showPoints || 
          entity.rendererComponents[0].completeText || 
          entity.rebuildMenuComponent && entity.rebuildMenuComponent.getRebuildButton(world) ||
          actionables.length > 0)){
        entity.healthComponent.kill();
    }
    
    _.chain(world.entities)
        .filter(function(worldEntity){
            return worldEntity.goalCompleteMenuComponent &&
                worldEntity.uid != entity.uid;
        })
        .each(function(worldEntity){
            worldEntity.healthComponent.kill();
        });

    if (this.source && this.source.uiDataComponent){
        entity.goalCompleteMenuComponent.path = this.source.uiDataComponent.lastPath;
        this.source = undefined;
    }
};

var traitImageForKey = function(traitKey, world){
    var trait = world.entityForUid(traitKey);
    if (trait){
        return trait.uiDataComponent.image;
    }
    else if (allArgs[traitKey]){
        return allArgs[traitKey].image;
    }
    return "trait";
};


var getRebuildButton = function(world){
    return _.chain(world.entities)
        .filter((worldEntity) => {
            return worldEntity.buttonControlComponent &&
                worldEntity.buttonControlComponent.entityKey == this.sourceKey;
        })
        .filter(function(button){
            return button.buttonControlComponent.canMakeEntity(world);                    
        })
        .first()
        .value();
};

var getRebuildFunction = function(ui){
    var world = ui.world;
    if (this.rebuildFunction === undefined){
        var button = this.getRebuildButton(ui.world);
        
        if (button){
            this.rebuildFunction = button.buttonControlComponent.getWrappedFunction(button, ui.world, ui);
        }
        else{
            this.rebuildFunction = false;
        }
    }
    
    return this.rebuildFunction;
};


var healEntities = function(entity, world){
    _.each(this.entitiesToHeal, function(val, key){
        _.each(world.entitiesForKey(key), function(entityToHeal){
            entityToHeal.healthComponent.health += val;
        });
    });
};


var healthBleedTick = function(entity){
    entity.healthComponent.health -= 1;
};


var killHealthComponent = function(){
    this.health = 0;
};


var healthModifyAmount = function(){
    return this.activated ? this.activeHealthChange : this.idleHealthChange;
};


var helpInfoPanelRender = function(rect, entity, ui){
    var panelButtons = [];

    ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);

    ctx.fillStyle = this.textColor;
    ctx.font = getFont(36);
    var titleWidth = ctx.measureText(entity.identityComponent.name).width;
    ctx.fillText(entity.identityComponent.name, rect[0] + (rect[2] - titleWidth) / 2, rect[1] + 50);
    
    ctx.font = getFont(20);
    _.each(this.helpLines, function(line, i){
        ctx.fillText(line, rect[0] + 10, rect[1] + 150 + 50 * i);
    });

    return panelButtons;
};


var openHelpPanel = function(entity, ui){
    var sourceEntity = ui.world.entityForUid(entity.panelComponent.sourceId);

    var panelKey = _.chain(csvIdentifiers.helpInfoPanel)
        .filter(function(helpInfoPanelKey){
            var helpInfoPanelArgs = allArgs[helpInfoPanelKey];
            return _.any(helpInfoPanelArgs.components, function(component){
                return sourceEntity[component];
            });
        })
        .first()
        .value();

    ui.expandPanel(panelKey);
};


var hitRender =  function(rect, entity, ui, rectCount){
    if (this.wasHit){
        var emotionRect = getEmotionRect(rect, entity, rectCount, ui);
        ctx.fillStyle = '#ffffff';
        ctx.fillRect(emotionRect[0], emotionRect[1], emotionRect[2], emotionRect[3]);
        this.wasHit = false;
    }
};


var imageRender = function(rect, entity){
    var image = getImage(this.image);
    ctx.drawImage(image, rect[0], rect[1]);
};


var impulseFocusStopInteractWithTrain = function(entity, world, train){
    var currencies = world.makeEntities(entity, this.impulseCurrency);
    var carriage = train.trainComponent.getCurrentCarriage(train, world);
    carriage.carriageComponent.holding = _.map(currencies, getUid);
    entity.healthComponent.health = 0;
    return false;
};

var impulseFocusStopCurrenciesMadeAtPoint = function(entity){
    return [this.impulseCurrency];
};


var resetEmotionSource = function(){
    this.spawnCount = 0;
};


var instantEmotionResourceTick = function(entity, world){
    entity.focusTickResponseComponent.focusTick(entity, world);
};


var canInteractSelf = function(){
    return this.canInteract(PARAMETERS.self);
};

var canInteract = function(key){
    return _.contains(this.interactionKeys, key);
};


var makeIsaacLifecycleEntities = function(entity, world){
    this.makeLifecycleEntities(entity, world, this.isaacEntities);
};


var junctionRender = function(rect){
    ctx.fillStyle = '#ff0000';
    ctx.beginPath();
    ctx.arc(rect[0] + rect[2] / 2, rect[1] + rect[3] / 2, 10, 0, 2 * Math.PI);
    ctx.fill();
};


var junctionItemForKey = function(key){
    for (var i = 0; i < this.heldItems.length; i++){
        if (this.heldItems[i].identityComponent.key == key &&
            this.heldItems[i].healthComponent.health > 0){
            return this.heldItems[i];
        }
    }
    return undefined;
};


var killCurrencyExec = function(entity, world){
    _.chain(world.entities)
        .filter((worldEntity) => {
            return (worldEntity.identityComponent && 
                    worldEntity.identityComponent.key == this.currencyToKill &&
                    !worldEntity.pathVelocityComponent.isHeld) ||
                (worldEntity.depositComponent && 
                 worldEntity.depositComponent.deposit[this.currencyToKill] &&
                 worldEntity.depositComponent.deposit[this.currencyToKill] > 0);
        })
        .each(function(worldEntity){
            worldEntity.healthComponent.health = 0;
        });
};


var dayEndKillEntity = function(entity, world){
    entity.healthComponent.health = 0;
};


var killEntity = function(entity){
    entity.healthComponent.kill();
};


var executeKillEntities = function(entity, world){
    _.each(this.entitiesToKill, function(key){
        _.each(world.entitiesForKey(key), function(entity){
            entity.healthComponent.kill();
        });
    });
};


var killIfFlagExec = function(entity, world){
    if (this.killIfFlag && world.flags(this.killIfFlag)){
        entity.healthComponent.kill();
    }
};


var killTrainExec = function(entity, world){
    _.chain(world.entities)
        .filter((entity) => {
            return entity.trainVelocityComponent &&
                _.contains(entity.trainVelocityComponent.trainStops, this.source.uid);
        })
        .each(function(entity){
            entity.healthComponent.kill();
        });
};


var killTraitExec = function(entity, world){
    _.chain(world.entities)
        .filter((worldEntity) => {
            return (worldEntity.identityComponent && _.contains(worldEntity.identityComponent.tags, this.traitToKill)) ||
                (worldEntity.managedComponent && worldEntity.managedComponent.managerKey == this.traitToKill) ||
                (worldEntity.identityComponent && worldEntity.identityComponent.key == this.traitToKill) ||
                (worldEntity.sourceExtensionComponent && worldEntity.sourceExtensionComponent.sourceKey == this.traitToKill);
        })
        .each(function(worldEntity){ 
            if (worldEntity.healthComponent){
                worldEntity.healthComponent.health = 0;
            }
        });
};


var lostControlDeathExec = function(entity, world){
    _.chain(world.entities)
        .filter(function(worldEntity){ return worldEntity.loseControlExec; })
        .each(function(worldEntity){ worldEntity.loseControlExec.execute(worldEntity, world); });
};


var makeGainControlMenuExec = function(entity, world){
    world.makeEntities(entity, this.gainControlMenuKey + '_' + this.emotion, {
        emotion: this.emotion
    });
};


var timeForceExec = function(entity, world){
    world.game.ticksPerTick = entity.timeForceComponent.ticksPerTick;
    world.game.tickSystemWrapper.running = true;
};


var makeAllGetReplaceKeys = function(){
    return csvIdentifiers[this.csv];
}


var makeCurrencyRequest = function(entity, world){
    var request = makeEcs(this.requestKey, {
        currencyRequested: _.clone(this.currencyRequested),
        x: entity.positionComponent.x,
        y: entity.positionComponent.y
    });
    world.addEntity(request);
};


var executeMakeEntities = function(entity, world){
    var args = {};

    if (entity.identityComponent){
        args.startEntityKey = entity.identityComponent.key;
    }
    

    var madeEntities = world.makeEntities(entity, this.entitiesToMake, args);
    if (this.adjacent && madeEntities.length > 0){
        var firstEntity = madeEntities[0];
        var pos = [firstEntity.positionComponent.x, firstEntity.positionComponent.y];
        var neighbors = _.chain(world.hexMap.neighbors(pos[0], pos[1]))
            .filter(function(neighbor){
                return world.isPosBuildable(neighbor);
            })
            .shuffle()
            .value();
        
        for (var i = 1; i < madeEntities.length; i++){
            if (madeEntities[i].positionComponent){
                madeEntities[i].positionComponent.x = neighbors[i - 1][0];
                madeEntities[i].positionComponent.y = neighbors[i - 1][1];
            }
        }
    }
};


var makeIfMissingExec = function(entity, world){
    if (!world.entityForKey(this.missingKey)){
        world.makeEntities(this.source, this.entitiesToMake);
    }
};


var makeParticleEffect = function(entity, world){
    var rect =  world.hexMap.cellForPosComp(entity.positionComponent).rect;
    world.makeEntities(entity, this.particleKey, {
        effectPos: [rect[0] + rect[2] / 2, rect[1] + rect[3] / 2]
    });
};


var makeRandomMultipleGetReplaceKeys = function(entity, world){
    var numberOfProblems = (Math.random() * (this.count - this.minCount)) + this.minCount;
    var replaceKeys = [];
    for (var i = 0; i < numberOfProblems; i++){
        replaceKeys.push(this.entitiesToMake);
    }
    return replaceKeys;
};


var addExperienceToManager = function(entity, world){
    var managingEntity = world.entityForKey(this.managerKey);
    
    if (!managingEntity){
        managingEntity = _.chain(world.entities)
            .filter(function(worldEntity){
                return (worldEntity.sourceExtensionComponent || worldEntity.entityManagerComponent) &&
                    worldEntity.positionComponent.comparePos(entity.positionComponent);
            })
            .first()
            .value();
        if (managingEntity && managingEntity.sourceExtensionComponent){
            managingEntity = world.entityForKey(managingEntity.sourceExtensionComponent.sourceKey);
        }
    }

    if (managingEntity){
        managingEntity.experienceComponent.gainExp(managingEntity, world);
    }
};

var isManagingEntity = function(managingEntity){
    return (managingEntity.identityComponent && managingEntity.identityComponent.key == this.managerKey) ||
        (managingEntity.sourceExtensionComponent && managingEntity.sourceExtensionComponent.sourceKey == this.managerKey);
};


var mapSpawnPos = function(entity, world){
    return _.chain(world.hexMap.neighbors(entity.positionComponent.x,
                                          entity.positionComponent.y))
        .filter(function(neighbor){ return world.isPosBuildable(neighbor); })
        .sample()
        .value();
};

var mapSpawn = function(entity, world, spawnedEntities){
    var pos = this.mapSpawnPos(entity, world);

    if (spawnedEntities){
        var attempts = 0;
        while (_.any(spawnedEntities, function(spawnedEntity){ return spawnedEntity.positionComponent.isPos(pos); })){
            attempts += 1;
            if (attempts > 10){
                pos = undefined;
                break;
            }
            pos = this.mapSpawnPos(entity, world);
        }
    }

    if (pos){
        var key = _.isFunction(this.mapSpawnKey) ? this.mapSpawnKey(entity, world) : this.mapSpawnKey;
        if (key){
            var newEntity = makeEcs(key, {
                x: pos[0],
                y: pos[1],
                source: entity
            });
            world.addEntity(newEntity);
            this.mapPostSpawn(entity, world, newEntity);
            return newEntity;
        }
    }
    return false;
};


var markTraitNewExec = function(entity, world){
    var managerKey = entity.buttonControlComponent.managerKey;
    var manager = world.entityForKey(managerKey);
    if (manager && entity.healthComponent.health > 0){
        if (manager.interactionComponent.canInteractSelf() && entity.interactionComponent.canInteractSelf()){
            manager.entityManagerComponent.hasNew = true;
        }

        var rect =  world.hexMap.cellForPosComp(manager.positionComponent).rect;
        world.makeEntities(manager, this.particleEffect, {
            managerKey: managerKey,
            effectPos: [rect[0] + rect[2] / 2, rect[1] + rect[3] / 2]
        });

        _.chain(world.entities)
            .filter(worldEntity => worldEntity.sourceExtensionComponent && worldEntity.sourceExtensionComponent.sourceKey == managerKey)
            .each((worldEntity) => {
                var rect =  world.hexMap.cellForPosComp(worldEntity.positionComponent).rect;
                world.makeEntities(manager, this.particleEffect, {
                    managerKey: managerKey,
                    effectPos: [rect[0] + rect[2] / 2, rect[1] + rect[3] / 2]
                });
            });
    }
};


var flashbackFunction = function(entity, world){
    if (entity.entityManagerComponent.disabled){
        return;
    }
    
    var emotion = _.sample(entity.memoryComponent.memoryEmotions);

    if (!emotion){
        return;
    }

    for (var i = 0; i < this.flashbackCount; i++){
        var space = entity.entityManagerComponent.findFreeManagedSpace(entity, world);

        if (space){
            world.makeEntities(entity, 'flashback_' + emotion, {
                x: space.positionComponent.x,
                y: space.positionComponent.y,
                sourceTrait: entity.identityComponent.key
            });
        }
    }
};


var memoryTick = function(entity, world){
    var managedEntitiesCount = _.filter(world.entities, function(worldEntity){
        return worldEntity.managedComponent &&
            worldEntity.managedComponent.managerKey == entity.identityComponent.key;
    }).length;
    
    if (managedEntitiesCount > this.lastManagedCount){
        if (entity.timedDeathComponent){
            entity.timedDeathComponent.timeToDeath += 50 * managedEntitiesCount - this.lastManagedCount;
        }
        this.lastManagedCount = managedEntitiesCount;
    }
};


var memoryStartLifecycle = function(entity, world){
    this.entitiesMade = _.chain(world.entities)
        .filter(function(emotionResourceMap){
            return emotionResourceMap.emotionResourceMapComponent &&
                _.contains(entity.memoryComponent.memoryEmotions, emotionResourceMap.emotionResourceMapComponent.emotionToMap);
        })
        .map(function(emotionResourceMap){
            return world.makeEntities(entity, emotionResourceMap.emotionResourceMapComponent.emotionMappedEntities);
        })
        .flatten(true)
        .map(function(entity){
            return entity.uid;
        })
        .value();
};


var memoryFindPositionExec = function(entity, world){
    var checkedPositions = [];
    var positionsToCheck = [this.startPos];
    var blockingEntities = world.getBlockingEntities();
    var blockingPositions = _.map(blockingEntities, 
                                  blockingEntity => [blockingEntity.positionComponent.x, blockingEntity.positionComponent.y]);

    for (var j = 0; j < this.iterations; j++){
        var nextPositions = [];
        for (var i = 0; i < positionsToCheck.length; i++){
            var checkPos = positionsToCheck[i];

            var posNeighbors = world.neighbors(checkPos);
            var allNeighbors = _.chain(posNeighbors)
                .map(posNeighbor => world.neighbors(posNeighbor))
                .flatten(true)
                .filter(posNeighbor => world.isPosInBounds(posNeighbor))
                .value();

            if (_.all(allNeighbors, neighbor => !_.any(blockingPositions, blockingPos => comparePos(neighbor, blockingPos)))){
                entity.positionComponent.x = checkPos[0];
                entity.positionComponent.y = checkPos[1];
                return;
            }
            else{
                checkedPositions.push(checkPos);
                _.chain(world.neighbors(checkPos))
                    .filter(newPos => world.isPosInBounds(newPos))
                    .filter(newPos => !_.any(checkedPositions, checkedPos => comparePos(checkedPos, newPos)))
                    .filter(newPos => !_.any(nextPositions, checkedPos => comparePos(checkedPos, newPos)))
                    .each(function(newPos){ nextPositions.push(newPos); });
            }
        }
        positionsToCheck = nextPositions;
    }

    if (entity.healthComponent){ //Could not find an open position
        entity.healthComponent.kill();
    }
};


var memoryConstruct = function(entity, world){
    var newEntity = makeEcs(this.constructionIdentifier, {
        x: entity.positionComponent.x,
        y: entity.positionComponent.y,
        sourceTrait: this.sourceKey
    });
    world.addEntity(newEntity);
};


var memoryFadeExec = function(entity, world){
    var newTrait = world.transformTrait(this.sourceKey, this.newTrait, true, true);
    if (newTrait){
        newTrait.fadingMemoryComponent.oldTrait = this.sourceKey;
    }
};


var memoryGoalCompleteExec = function(entity, world){
    if (!entity.managedComponent){
        return;
    }

    var source = world.entityForKey(entity.managedComponent.managerKey);

    if (!source){
        return;
    }
    
    if (this.flareMemory){
        source.memoryComponent.flashbackFunction(source, world);
    }
    if (this.killMemory){
        source.healthComponent.kill()
    }

    _.chain(world.entities)
        .filter((worldEntity) => (worldEntity.memoryFadeComponent &&
                                  worldEntity.memoryFadeComponent.sourceKey == source.identityComponent.key))
        .each((worldEntity) => {
            worldEntity.timedDeathComponent.timeToDeath += this.bonusTime;
        })
};


var triggerFlashback = function(entity, world){
    if (Math.random() < this.flashbackChance){
        var memory = world.entityForKey(entity.managedComponent.managerKey);
        if (memory && memory.memoryComponent){
            memory.memoryComponent.flashbackFunction(memory, world);
        }
    }
};


var menuTutorialTextRender = function(rect, entity, ui){
    this.hasDrawn = true;
    this.menuEntity = _.chain(ui.world.entities)
        .filter((worldEntity) => (worldEntity.panelComponent &&
                                  !(worldEntity.panelComponent.closed || worldEntity.panelComponent.minimized) &&
                                  worldEntity.identityComponent &&
                                  worldEntity.identityComponent.key.indexOf(this.entityToMark) == 0))
        .first()
        .value();
    
    if (!this.menuEntity){
        return;
    }

    this.shownOnce = true;

    rect = this.menuEntity.absolutePositionComponent.getAbsPosRect();

    ctx.fillStyle = '#ffffff';
    ctx.font = getFont((this.text.length > 10) ? 20 : 30);
    var textWidth = ctx.measureText(this.text).width;
    ctx.fillText(this.text, min(rect[0] + this.xOffset, width - textWidth - 10), rect[1] + this.yOffset - 20);
    ctx.font = BASE_FONT;
    
    if (this.shouldDrawArrow){
        this.drawArrow([rect[0] + this.xOffset + 100, rect[1] + this.yOffset - 10],
                       [rect[0] + this.xOffset, rect[1] + this.yOffset]);
    }
};


var menuTutorialDone = function(entity, world){
    var renderComponent = entity.rendererComponents[0];
    if (renderComponent.hasDrawn && renderComponent.menuEntity == undefined){
        if (!renderComponent.shownOnce){
            entity.tutorialTextComponent.nextEntities = undefined;
        }
        return true;
    }
    return false;
};


var nameRender = function(rect){
    ctx.fillStyle = '#ffffff';
    ctx.fillText(this.name, rect[0] + rect[2] / 3, rect[1] + rect[3] / 4);
};


var nextDayEntityDayEnd = function(entity, world){
    _.each(this.execs, function(exec){
        exec.execute(entity, world);
    });
    entity.healthComponent.health = 0;
};


var noFocusNotifierRender = function(rect, entity, ui){
    var self = ui.entityForKey(PARAMETERS.self);
    if (self.focusMakingComponent.focusUsing === 0){
        var entitiesToMark = [];
        var goalButtons = [];

        _.chain(ui.world.entities)
            .filter(worldEntity => {
                if (worldEntity.worldGoalComponent){
                    return true;
                }
                else if (worldEntity.damageComponent && worldEntity.damageComponent.charges < worldEntity.damageComponent.maxCharges){
                    return true;
                }
                return false;
            })
    }
};


var notificationSerialize = function(entity, serialized){
    delete serialized.notificationFunctions;
    return serialized;
};


var addNotificationCurrency = function(entity, args, world){
    world.addCost(entity.notificationCurrencyComponent.currencyName, 
                  entity.notificationCurrencyComponent.notificationCurrency);
};


var notifyEntityMakerFn = function(entity, args, world){
    if (_.all(this.notificationArgs, function(val, key){
        return args[key] == val;
    })){
        this.count -= 1;
        if (this.count <= 0){
            this.count = this.impulseCount;
            world.makeEntities(entity, this.entitiesToMake);
        }
    }
};


var openMenuExecute = function(entity, world){
    if (entity.interactionComponent && entity.interactionComponent.canInteract(PARAMETERS.self)){
        var markerEntity = makeEcs(this.markerKey, {
            entityUid: entity.uid,
            x: entity.positionComponent.x,
            y: entity.positionComponent.y
        });
        world.addEntity(markerEntity);
    }
};


var openMenuOnClick = function(entity, world, ui){
    ui.openPanelAtPos('#' + entity.identityComponent.key, entity.positionComponent);
};


var openMenuMarkerRender = function(rect, entity, ui){
    var entityToClick = ui.world.entityForUid(entity.openMenuMarkerComponent.entityUid);
    if (entityToClick){
        entityToClick.clickComponent.onClick(entityToClick, ui.world, ui);
        entity.healthComponent.health = 0;
    }
};


var generateOpinionIsaacs = function(args){
    var isaacEntities = (args.isaacEntities !== undefined) ? args.isaacEntities : [];
    var mangle = function(key){
        return mangleIdentityWithTrait(key, args.personHeld);
    };
    
    isaacEntities = isaacEntities.concat(_.map(args.buttonKeys, mangle));
    isaacEntities = isaacEntities.concat(_.map(args.opinionActions, mangle));
    
    return isaacEntities;
};


var opinionTick = function(entity, world){
    if (tickComp(this)){
        var person = world.entityForKey(entity.personHeldComponent.personHeld);
        world.makeEntities(person, this.emotionsToMake);
    }
};


var opinionConditionalExec = function(entity, world){
    if (_.any(this.opinions, (opinionKey) => {
        return _.any(world.entitiesForKey(opinionKey), (opinion) => {
            opinion.personHeldComponent.personHeld == this.person;
        });
    })){
        var source = world.entityForUid(this.sourceId);
        world.makeEntities(source, this.entitiesToMake);
    }
};


var pairTransformGetReplaceKeys = function(entity, world){
    var trait1 = world.entityForKey(this.trait1);
    var trait2 = world.entityForKey(this.trait2);

    if (!(trait1 && trait2)){
        return false;
    }

    var trait1InteractionKeys = trait1.interactionComponent.interactionKeys;
    var trait2InteractionKeys = trait2.interactionComponent.interactionKeys;

    var setTrait = function(trait, interactionKeys){
        trait.interactionComponent.interactionKeys = interactionKeys;
        
        _.chain(world.entities)
            .filter(worldEntity => (worldEntity.buttonControlComponent &&
                                    worldEntity.buttonControlComponent.managerKey == trait.identityComponent.key))
            .each(worldEntity => {
                worldEntity.interactionComponent.interactionKeys = interactionKeys;
            });

        _.chain(world.entities)
            .filter(worldEntity => (worldEntity.healthComponent &&
                                    worldEntity.managedComponent &&
                                    worldEntity.managedComponent.managerKey == trait.identityComponent.key))
            .each(worldEntity => worldEntity.healthComponent.kill());
    }

    setTrait(trait1, trait2InteractionKeys);
    setTrait(trait2, trait1InteractionKeys);

    return false;
};


var expandPanel = function(){
    this.minimized = false;
    this.closed = false;
};


var panelUiClick = function(pos, entity, ui){
    _.each(entity.panelComponent.panelButtons, function(pair){
        var rect = pair[0];
        var fn = pair[1];

        if (containsPos(rect, pos)){
            if (ui.gameProperties.settings.sfx){
                var pingEffect = getAudio.get('sounds/beep.ogg');
                pingEffect.play();
            }

            fn(entity, ui);
        }
    });
};


var snapPanelPos = function(entity){
    entity.absolutePositionComponent.y = min(entity.absolutePositionComponent.y, 678 - entity.absolutePositionComponent.size[1]);
    entity.absolutePositionComponent.y = max(entity.absolutePositionComponent.y, 50 - entity.absolutePositionComponent.size[1]);
    entity.absolutePositionComponent.x = min(entity.absolutePositionComponent.x, width - entity.absolutePositionComponent.size[0]);
    entity.absolutePositionComponent.x = max(entity.absolutePositionComponent.x, 50 - entity.absolutePositionComponent.size[0]);
};

var outPanelRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    entity.panelComponent.panelButtons = [];
    if (entity.panelComponent.closed){
        this.renderTime = 0;
        return;
    }

    this.snapPanelPos(entity);

    if (this.renderTime < this.fullAnimationTime){
        this.renderTime += lastInterval;
    }

    if (this.renderTime < this.animatingTime){
        this.renderTime += lastInterval;
        var fraction = this.renderTime / this.animatingTime;
        ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2] * fraction, rect[3] * fraction);
        return;
    }

    var panelButtons = this.specificRender(rect, entity, ui);
    
    if (panelButtons !== false){
        entity.panelComponent.panelButtons = panelButtons;
        
        if (entity.panelComponent.panelButtons !== undefined){
            ctx.font = getFont(20);
            
            var closeRect = [rect[0] + rect[2] / 2 - 50, rect[1] + rect[3] - 50, 100, 40];
            this.drawPanelButton(closeRect, 'CLOSE');
            
            entity.panelComponent.panelButtons.push([closeRect, entityPanelClose]);
        }
    }

    ctx.font = BASE_FONT;
};

var drawPanelButton = function(rect, text){
    ctx.strokeStyle = this.insetColor;
    ctxRoundedRect(rect);
    
    ctx.fillStyle = this.textColor;
    var measure = ctx.measureText(text);
    var textWidth = measure.width;
    ctx.fillText(text, rect[0] + rect[2] / 2 - textWidth / 2, rect[1] + rect[3] / 2 + this.yTextOffset);
};

var drawImageButton = function(rect, img){
    ctx.strokeStyle = this.insetColor;
    ctxRoundedRect(rect);

    img = getImage(img);

    ctx.drawImage(img, rect[0] + (rect[2] - img.width) / 2, rect[1] + (rect[3] - img.height) / 2);
}

var entityPanelClose = function(entity, ui){
    entity.panelComponent.closed = true;
    
    if (entity.panelComponent.sourceId){
        var building = ui.world.entityForUid(entity.panelComponent.sourceId);
        if (building && building.menuCloseComponent){
            building.menuCloseComponent.execute(building, ui.world);
        }
    }
};

var drawHelpButton = function(entity, rect){
    ctx.font = getFont(16);
    var helpRect = [rect[0] + rect[2] - 25, rect[1], 25, 25];
    ctx.strokeStyle = this.insetColor;
    ctx.beginPath();
    ctx.moveTo(helpRect[0], helpRect[1]);
    ctx.lineTo(helpRect[0], helpRect[1] + helpRect[3]);
    ctx.lineTo(helpRect[0] + helpRect[2], helpRect[1] + helpRect[3]);
    ctx.stroke();

    ctx.fillStyle = this.textColor;
    ctx.fillText('?', helpRect[0] + 7, helpRect[1] + helpRect[3] - 5);
    return [helpRect, entity.helpPanelComponent.openHelpPanel];
};


var paramPathExec = function(entity, world){
    var source = world.entityForKey(this.startKey);
    var end = world.entityForKey(this.endKey);
    if (source && end){
        var trainPath = makeEcs(this.trainPathKey, {
            trainStops: [end.uid],
            trainStartId: source.uid,
            focusCost: 0,
            stoppable: false
        });
        world.addEntity(trainPath);
    }
};


var makeParticle = function(){
    return {
        timeLeft: Math.random() * 0.5 + 0.5,
        radial: (Math.random() * 0.5 + 0.5) * this.effectRadius,
        angular: Math.random() * 2 * Math.PI
    };
};

var particleEffectRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    while (this.startParticles > 0){
        this.startParticles -= 1;
        this.particles.push(this.makeParticle());
    }
    this.timeToCheck -= lastInterval;
    if (this.timeToCheck < 0){
        this.timeToCheck += this.impulseTimeToCheck;
        this.particles.push(this.makeParticle());
    }
    
    this.particles = _.filter(this.particles, function(particle){
        particle.timeLeft -= lastInterval;
        return (particle.timeLeft > 0);
    });


    var image = getImage(this.image);
    _.each(this.particles, (particle) => {
        var x = particle.radial * Math.cos(particle.angular);
        var y = particle.radial * Math.sin(particle.angular);
        ctx.drawImage(image, this.effectPos[0] + x, this.effectPos[1] + y);
    });
};


var pathConnectRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    if (ui.world.lostControl()){
        return;
    }

    var endRect = ui.world.hexMap.cellForPosComp({x: entity.pathMoveComponent.endPosition[0],
                                                  y: entity.pathMoveComponent.endPosition[1]}).rect;
    var startPos = [rect[0] + rect[2] / 2, rect[1] + rect[3]/ 2];
    var endPos = [endRect[0] + endRect[2] / 2, endRect[1] + endRect[3] / 2];

    if (this.timeDrawn < 0.33){
        endPos = [
            startPos[0] + (endPos[0] - startPos[0]) * this.timeDrawn * 3,
            startPos[1] + (endPos[1] - startPos[1]) * this.timeDrawn * 3,
        ]
        this.timeDrawn += lastInterval;
    }

    ctx.strokeStyle = '#0099ff';
    ctx.beginPath();
    ctx.moveTo(startPos[0], startPos[1]);
    ctx.lineTo(endPos[0], endPos[1]);
    ctx.stroke();
};


var duplicatePathExec = function(entity, world){
    var chk = _.any(world.entities, function(worldEntity){
        return worldEntity.pathMoveComponent &&
            ((comparePos(worldEntity.pathMoveComponent.startPosition, entity.pathMoveComponent.startPosition) &&
              comparePos(worldEntity.pathMoveComponent.endPosition, entity.pathMoveComponent.endPosition)) ||
             (comparePos(worldEntity.pathMoveComponent.startPosition, entity.pathMoveComponent.endPosition) &&
              comparePos(worldEntity.pathMoveComponent.endPosition, entity.pathMoveComponent.startPosition)));
            
    });
    if (chk){
        entity.healthComponent.health = 0;
    }
};


var pathButtonRender = function(rect, entity, ui){
    if (!this.pathButtonFunction){
        this.pathButtonFunction = function(){
            ui.world.togglePathBuilding();
        };
    }
    ui.informLine(PARAMETERS.buildPanel, {
        buttonFunction: this.pathButtonFunction,
        text: 'path'
    }, PARAMETERS.baseButtonName);
};


var pathMakerGetReplaceKeys = function(entity, world){
    var start = world.entityForKey(this.startKey);
    var end = world.entityForKey(this.endKey);
    world.connectEntities(start, end);
};


var pathStopRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    if (this.timeDrawn < this.renderTime){
        this.timeDrawn += lastInterval;
        
        var scale = 1;
        if (this.timeDrawn < this.renderTime / 2){
            scale += 0.25 * this.timeDrawn * 2 / this.renderTime;
        }
        else{
            scale += 0.25 * (this.renderTime - this.timeDrawn) * 2 / this.renderTime;
        }
        var newRect = [rect[0] - rect[2] * (scale - 1) / 2, rect[1] - rect[3] * (scale - 1) / 2, rect[2] * scale, rect[3] * scale];
        ctx.drawImage(getImage(this.image), newRect[0], newRect[1], newRect[2], newRect[3]);
    }
    else{
        var image = getImage(this.isActive ? this.activeImage : this.image);
        var x = rect[0] + this.getWobble(lastInterval);

        ctx.drawImage(image, x, rect[1]);

        if (this.outlineImage){
            ctx.drawImage(getImage(this.outlineImage), x, rect[1]);
            this.outlineImage = false;
            
            this.renderTrainIcon(lastInterval, x, rect[1]);
        }
        else{
            if (this.blinkingOutlineImage){
                this.blinkingOutlineTime -= lastInterval;
                if (this.blinkingOutlineTime > 0){
                    ctx.globalAlpha = this.blinkingOutlineTime;
                    ctx.drawImage(getImage(this.blinkingOutlineImage), x, rect[1]);
                    ctx.globalAlpha = 1;
                }
                else{
                    this.blinkingOutlineImage = false;
                }
            }
        }
    }
    this.isActive = false;
};

var renderTrainIcon = function(lastInterval, x, y){
    ctx.fillStyle = this.trainColor;
    ctx.beginPath();
    ctx.arc(x + 30, y + 26, 4, 0, 2 * Math.PI);
    ctx.fill();
};


var getAllPersonality = function(){
    return _.map(this.heldOpinions, getEntityKey).concat(this.personalityTraits);
};

var addOpinionToPersonality = function(opinionKey, entity, world){
    var opinion = makeEcs(opinionKey, {
        personHeld: entity.identityComponent.key
    });
    world.addEntity(opinion);
    this.heldOpinions.push(opinion);
};


var personCreateExec = function(entity, world){
    entity.personalityComponent.heldOpinions = _.map(entity.personalityComponent.heldOpinions, function(opinion){
        opinion = makeEcs(opinion, {
            personHeld: entity.identityComponent.key
        });
        world.addEntity(opinion);
        return opinion;
    });
};


var personKillExec = function(entity, world){
    _.each(entity.personalityComponent.heldOpinions, function(opinion){
        opinion.healthComponent.health = 0;
    });
};


var derivePersonGoalCurrencies = function(currencies, personKey){
    var derivedCurrencies = {};
    _.each(currencies, function(val, key){
        var currencyKey;
        
        if (_.contains(csvIdentifiers.currency, key)){
            currencyKey = key;
        }
        else{
            currencyKey = key + '_' + personKey;
        }

        derivedCurrencies[currencyKey] = val;
    });
    return derivedCurrencies;
};


var addOpinionExec = function(entity, world){
    var person = world.entityForKey(entity.personHeldComponent.personHeld);

    var opinionsToMake = [];
    
    if (this.opinionToAdd){
        opinionsToMake.push(this.opinionToAdd);
    }

    var existingOpinions = [];

    _.each(person.personalityComponent.heldOpinions, function(opinion){
        existingOpinions.push(opinion.identityComponent.key);
        _.each(opinion.opinionComponent.successOpinions, function(val, key){
            if (percentCheck(val)){
                opinionsToMake.push(key);
            }
        });
    });

    _.chain(opinionsToMake)
        .uniq()
        .filter(function(key){
            return !_.contains(existingOpinions, key);
        })
        .each(function(key){
            person.personalityComponent.addOpinionToPersonality(key, person, world);
        });
};


var placedTowerGetReplaceKeys = function(entity, world, args){
    var managedEntities = _.filter(world.entities.concat(world.entitiesToAdd), worldEntity => worldEntity.managedComponent);
    var spaces = _.chain(world.entities)
        .filter(worldEntity => (
            ((worldEntity.identityComponent && worldEntity.identityComponent.key == this.sourceTrait) ||
             (worldEntity.sourceExtensionComponent && worldEntity.sourceExtensionComponent.sourceKey == this.sourceTrait)) &&
                (_.all(managedEntities, managedEntity => !managedEntity.positionComponent.comparePos(worldEntity.positionComponent)))
        ))
        .map(worldEntity => [worldEntity.positionComponent.x, worldEntity.positionComponent.y])
        .value();
    var space = _.min(spaces, pos => Math.abs(pos[0] - this.x) + Math.abs(pos[1] - this.y));
    
    if (space){
        args.x = space[0];
        args.y = space[1];
        args.sourceKey = this.sourceTrait;
        args.sourceTrait = this.sourceTrait;
        return this.tower;
    }
    else{
        return false;
    }
};


var addPoints = function(entity, args){
    entity.pointCounterComponent.points += args.points;
};


var setPos = function(pos){ 
    this.x = pos[0];
    this.y = pos[1]; 
};

var isPos = function(pos){
    return this.x == pos[0] && this.y == pos[1];
};

var compComparePos = function(positionComponent){
    return this.x == positionComponent.x &&
        this.y == positionComponent.y;
};

var assertPosition = function(val, idx){
    if (val !== undefined){
        if (val < 0 || val >= PARAMETERS.size[idx]){
            console.log('impossible position ' + val);
            throw "Impossible position " + val;
        }
    }
    return val;
};

var getAsPos = function(){
    return [this.x, this.y];
};


var printKillExec = function(entity, world){
    console.log(entity);
};


var progressRender = function(rect, entity){
    ctx.fillStyle = '#ffffff';
    var percentComplete = entity.focusPathStopComponent.percentComplete(entity);
    if (percentComplete > 0){
        ctx.fillText('' + percentComplete + '%', rect[0] + this.progressX, rect[1] + this.progressY);
    }
};


var improveQuality = function(entity, world){
    var qualityEntity;
    if (this.qualityEntity){
        qualityEntity = world.entityForKey(this.qualityEntity);
    }
    else{
        qualityEntity = _.filter(world.entities, function(worldEntity){
            return worldEntity.qualityComponent &&
                worldEntity.managedComponent &&
                worldEntity.managedComponent.managerKey == entity.personHeldComponent.personHeld;
        })[0];
    }

    if (this.checkAgainstQuality){
        qualityEntity.qualityComponent.quality += this.qualityDiff;
    }

    if (this.entitiesToMake){
        world.makeEntities(qualityEntity, this.entitiesToMake);
    }
};


var qualityResourceTick = function(entity, world){
    if (this.lastQuality <= this.qualityThreshold && entity.qualityComponent.quality > this.qualityThreshold){
        entity.resourceComponent.resourceKeys = this.highQualityResources;
        world.makeEntities(entity, this.flipEntities);
    }
    else if (this.lastQuality > this.qualityThreshold && entity.qualityComponent.quality <= this.qualityThreshold){
        entity.resourceComponent.resourceKeys = this.lowQualityResources;
        _.each(this.flipEntities, function(flipEntity){
            _.each(world.entitiesForKey(flipEntity), function(worldEntity){
                worldEntity.healthComponent.health = 0;
            });
        });
    }

    this.lastQuality = entity.qualityComponent.quality;
};


var qualityRender = function(rect, entity){
    ctx.fillStyle = '#ffffff';
    ctx.fillText(entity.qualityComponent.quality, rect[0], rect[1] + rect[3])
};


var randomCarouselSetMarker = function(marker){
    var goodCount = marker.randomMenuComponent.goodCount;
    var badCount = marker.randomMenuComponent.badCount;
    
    var badToAdd = 0;
    this.carousel = [];
    for (var i = 0; i < goodCount; i++){
        this.carousel.push(this.goodImage);
        badToAdd += badCount;
        while (badToAdd >= goodCount){
            this.carousel.push(this.badImage);
            badToAdd -= goodCount;
        }
    }
    
    this.goodResult = marker.randomMenuComponent.goodResult;
};

var randomCarouselRender = function(pos, renderTime){
    var finalImage = this.goodResult ? this.goodImage : this.badImage;
    if (this.flatDisplayStartTime){
        if (renderTime > this.flatDisplayStartTime + 1){
            this.animationDone = true;
        }
    }

    var timeToRender = this.flatDisplayStartTime ? this.flatDisplayStartTime : renderTime;
    var index = timeToRender / 0.3;
    var flatIndex = Math.floor(index);
    var fraction = index - flatIndex;
    flatIndex = this.carousel.length - flatIndex;
    while (flatIndex < 0){
        flatIndex += this.carousel.length;
    }
        
    var images = _.map(_.range(4), (i) => {
        var idx = (flatIndex + i) % this.carousel.length;
        return this.carousel[idx];
    });

    if (renderTime > this.animationTime && images[2] == finalImage && !this.flatDisplayStartTime){
        this.flatDisplayStartTime = renderTime;
    }

    var rect = [pos[0], pos[1], this.imgSize[0], this.imgSize[1]];

    
    var sheight = fraction * rect[3];

    ctx.drawImage(getImage(images[0]), 
                  0, rect[3] - sheight, rect[2], sheight,
                  rect[0], rect[1], rect[2], sheight);

    ctx.drawImage(getImage(images[1]), rect[0], rect[1] + sheight);
    ctx.drawImage(getImage(images[2]), rect[0], rect[1] + rect[3] + sheight);
    ctx.drawImage(getImage(images[3]),
                  0, 0, rect[2], rect[3] - sheight,
                  rect[0], rect[1] + 2 * rect[3] + sheight, rect[2], rect[3] - sheight);
    ctx.drawImage(getImage(this.frameImage), rect[0], rect[1]);
};


var randomIfNoneGetReplaceKeys = function(entity, world){
    if (_.all(this.keys, key => !world.entityForKey(key))){
        return _.sample(this.keys);
    }
};


var randomOpenPositionExec = function(entity, world){
    var pos;
    if (entity.managedComponent){
        var allEntities = world.entities.concat(world.entitiesToAdd);
        pos = _.chain(allEntities)
            .filter(function(worldEntity){
                return entity.managedComponent.isManagingEntity(worldEntity) &&
                    !_.any(allEntities, function(chkEntity){
                        return (chkEntity.managedComponent || chkEntity.goalTrackComponent) &&
                            chkEntity.positionComponent.comparePos(worldEntity.positionComponent);
                    });
            })
            .map(function(worldEntity){
                return [worldEntity.positionComponent.x, worldEntity.positionComponent.y];
            })
            .sample()
            .value();
    }
    
    if (!pos){
        if (this.startEntityKey){
            this.startPos = world.findStartPos(this.startEntityKey);
        }
        if (this.startPos && (this.startPos[0] === undefined || this.startPos[1] === undefined)){
            this.startPos = undefined;
        }
        pos = world.randomOpenPosition(entity.positionComponent.planeOfExistence, this.startPos, this.minSelfDist);
    }
    
    entity.positionComponent.setPos(pos);
};


var randomReplaceGetReplaceKeys = function(entity, world){
    var convertEntityGroup = function(entityGroup, entities){
        if (_.isString(entityGroup)){
            entities[entityGroup] = 1;
            return 1;
        }
        else if (_.isArray(entityGroup)){
            _.each(entityGroup, function(entityKey){ entities[entityKey] = 1; });
            return entityGroup.length;
        }
        else{
            _.each(entityGroup, function(val, key){
                entities[key] = val;
            });
            return _.reduce(entityGroup, addFunction, 0);
        }
    };
    
    var entities = {};

    var goodCount = convertEntityGroup(this.goodEntities, entities);
    var badCount = convertEntityGroup(this.badEntities, entities);

    var chanceSum = _.reduce(entities, addFunction, 0);
    var randVal = Math.random() * chanceSum;
    var keys = _.keys(entities);
    var keyToReturn = undefined;
    for (var i = 0; i < keys.length; i++){
        var key = keys[i];
        var chance = entities[key];
        if (chance > randVal){
            keyToReturn = key;
            break;
        }
        else{
            randVal -= chance;
        }
    }

    if (keyToReturn === undefined){
        throw 'random replace failed';
        return false;
    }

    var goodEntities = {};
    convertEntityGroup(this.goodEntities, goodEntities);
    var goodResult = goodEntities[keyToReturn] !== undefined;

    
    if (this.addReplaceMarker){
        world.makeEntities(entity, PARAMETERS.randomMenuMarker, {
            goodCount: goodCount,
            badCount: badCount,
            goodResult: goodResult
        });
    }

    return keyToReturn;
};


var currenciesAtResource = function(entity, world){
    return _.chain(this.resourceKeys)
        .map((resourceKey) =>  _.filter(world.entities, (worldEntity) => (
                worldEntity.identityComponent &&
                worldEntity.identityComponent.sourceId == entity.uid &&
                worldEntity.identityComponent.key == resourceKey &&
                worldEntity.positionComponent.comparePos(entity.positionComponent) &&
                !worldEntity.pathVelocityComponent.isHeld
            )))
        .flatten(true)
        .value();
};


var resourceInteractWithTrain = function(entity, world, train){
    var trainRequirements = train.trainComponent.calculateTrainCurrencies(train, world);

    var currenciesAtResource = entity.resourceComponent.currenciesAtResource(entity, world);

    for (var i = 0; i < entity.resourceComponent.resourceKeys.length; i++){
        var resourceKey = entity.resourceComponent.resourceKeys[i];

        if (trainRequirements[resourceKey] === undefined){
            continue;
        }

        var carriage = train.trainComponent.getCurrentCarriage(train, world);
        var waiting = true;

        _.chain(currenciesAtResource)
            .filter((worldEntity) => !_.contains(carriage.carriageComponent.holding, worldEntity.uid))
            .first(1)
            .each(function(worldEntity){
                carriage.carriageComponent.holding.push(worldEntity.uid);
                worldEntity.pathVelocityComponent.isHeld = true;
                waiting = false;
            });

        return waiting;
    }
    return false;
};

var resourceCurrenciesMadeAtPoint = function(entity){
    return entity.resourceComponent.resourceKeys;
};


var resourceFocusTick = function(entity, world){
    if (entity.resourceComponent.resourceCount !== undefined && entity.resourceComponent.resourceCount <= 0){
        entity.healthComponent.kill()
        return;
    }

    var resourceCountAtPoint = _.filter(world.entities, (worldEntity) => (
        worldEntity.identityComponent &&
            _.contains(entity.resourceComponent.resourceKeys, worldEntity.identityComponent.key) &&
            worldEntity.positionComponent.comparePos(entity.positionComponent)
    )).length;

    if (resourceCountAtPoint < 5){
        world.makeEntities(entity, entity.resourceComponent.resourceKeys, {
            emotionTags: this.emotionTags,
            destination: this.destination,
            firstTick: this.firstTick
        });

        this.firstTick = false;
                
        if (entity.managedComponent){
            entity.managedComponent.addExperienceToManager(entity, world);
        }

        if (entity.resourceComponent.resourceCount){
            entity.resourceComponent.resourceCount -= 1;
        }
        
        world.makeEntities(entity, PARAMETERS.towerFlash);

        if (entity.resourceComponent.resourceEntities){
            world.makeEntities(entity, entity.resourceComponent.resourceEntities);
        }
    }
    else{
        this.focusTickHook(entity, world);
    }
};


var killResourceCurrencies = function(entity, world){
    _.each(entity.resourceComponent.currenciesAtResource(entity, world), 
           function(currencyEntity){
               currencyEntity.healthComponent.kill();
           });
};


var resourceModifierGetTowerForTickModifierRender = function(entity, world){
    return world.entityForUid(entity.resourceModifierComponent.towerId);
};


var revertMemoryExec = function(entity, world){
    var source = world.entityForUid(this.sourceId);
    var sourceTrait = world.entityForKey(source.managedComponent.managerKey);
    var memory = world.transformTrait(sourceTrait.identityComponent.key,
                                      sourceTrait.fadingMemoryComponent.oldTrait, true);
    if (memory){
        _.each(_.range(3), function(){
            memory.memoryComponent.flashbackFunction(memory, world);
        });
    }
};


var risingTextRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    this.currentHeight += this.height * lastInterval / this.effectTime;
    if (this.currentHeight > this.height){
        entity.healthComponent.kill();
    }
    else{
        ctx.fillStyle = '#ffffff';
        ctx.globalAlpha = 1 - 0.5 * (this.currentHeight / this.height);
        ctx.fillText(this.text, this.effectPos[0], this.effectPos[1] - this.currentHeight);
        ctx.globalAlpha = 1;
    }
};


var selfDamageEffectRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    if (this.timeDrawn > this.animationTime){
        entity.healthComponent.kill();
    }

    this.timeDrawn += lastInterval;
    var dist = (this.timeDrawn / (3 * this.animationTime)) + 1;
    dist *= 60;

    ctx.fillStyle = '#ff0000';
    for (var i = 0; i < 3; i++){
        var angle = i * Math.PI / 6;
        var x = dist * Math.cos(angle);
        var y = dist * Math.sin(angle);
        ctx.fillRect(x, y, 2, 2);
    }
};


var selfExtensionSetEmotion = function(emotion){
    this.emotion = emotion;
    this.emotionLife = 30;
};


var selfExtensionRender = function(rect, entity){
    if (entity.selfExtensionComponent.emotion){
        ctx.fillStyle = EMOTION_FILLSTYLES[entity.selfExtensionComponent.emotion];
    }
    else {
        ctx.fillStyle = '#ffffff';
    }

    ctx.fillRect(rect[0] + 10, rect[1] + 10, 20, 20);
};


var selfExtensionTick = function(entity){
    if (entity.selfExtensionComponent.emotionLife){
        entity.selfExtensionComponent.emotionLife -= 1;
        if (entity.selfExtensionComponent.emotionLife <= 0){
            entity.selfExtensionComponent.emotion = undefined;
        }
    }
};


var setFlagExec = function(entity, world){
    world.flags[this.flag] = this.flagValue;
};


var settingsPanelRender = function(rect, entity, ui){
    var panelButtons = [];
    var that = this;

    ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);
    
    ctx.fillStyle = '#000000';
    ctx.font = getFont(40);
    ctx.fillText('OPTIONS', rect[0] + rect[2] / 2 - ctx.measureText('OPTIONS').width / 2, 45);
    ctx.font = BASE_FONT;
    
    ctx.fillText('FULLSCREEN', rect[0] + 10, rect[1] + 85);

    var insetColor = (p) => p ? '#ff0000' : '#000000';

    var fullscreenRect = [rect[0] + 100, rect[1] + 70, 100, 30];

    if (!this.fullscreenFunction){
        this.fullscreenFunction = function(){
            if (ui.gameProperties.settings.fullscreen){
                document.webkitExitFullscreen();
            }
            else{
                canvas.webkitRequestFullscreen();
            }
            ui.gameProperties.settings.fullscreen = !ui.gameProperties.settings.fullscreen;
            ui.gameProperties.save();
        };
    }

    this.drawPanelButton(fullscreenRect, 'Toggle');
    panelButtons.push([fullscreenRect, this.fullscreenFunction]);

    ctx.fillText('SOUND', rect[0] + 10, rect[1] + 135);
    var soundRect = [rect[0] + 100, rect[1] + 120, 40, 40];

    if (!this.soundFunction){
        this.soundFunction = function(){
            if (ui.gameProperties.settings.music){
                ui.gameProperties.backgroundAudio.pause();
            }
            else{
                ui.gameProperties.backgroundAudio.play();
            }

            ui.gameProperties.settings.music = !ui.gameProperties.settings.music;
            ui.gameProperties.save();
        };
    }

    var soundVols = [0.05, 0.1, 0.2];
    if (!this.soundVolFunctions || this.soundVolFunctions.length === 0){
        this.soundVolFunctions = _.map(soundVols, function(vol){
            return function(){
                ui.gameProperties.settings.musicVolume = vol;
                ui.gameProperties.backgroundAudio.volume = vol;

                if (!ui.gameProperties.settings.music){
                    ui.gameProperties.settings.music = true;
                    ui.gameProperties.backgroundAudio.play();
                }

                ui.gameProperties.save();
            };
        });
    }

    this.insetColor = insetColor(!ui.gameProperties.settings.music);
    this.drawImageButton(soundRect, 'mute');
    panelButtons.push([soundRect, this.soundFunction]);
    
    _.each(this.soundVolFunctions, function(volFunction, i){
        var iRect = [soundRect[0] + 60 * (i + 1), soundRect[1], soundRect[2], soundRect[3]];
        that.insetColor = insetColor(ui.gameProperties.settings.music && ui.gameProperties.musicVolume == soundVols[i]);
        that.drawImageButton(iRect, 'sound' + i);
        panelButtons.push([iRect, volFunction]);
        that.insetColor = '#000000'
    });

    ctx.fillText('SFX', rect[0] + 10, rect[1] + 185);
    var sfxRect = [rect[0] + 100, rect[1] + 170, 40, 40];
    
    if (!this.sfxFunction){
        this.sfxFunction = function(){
            ui.gameProperties.settings.sfx = !ui.gameProperties.settings.sfx;
            ui.gameProperties.save();
        }
    }

    var sfxVols = [0.25, 0.5, 0.75];
    if (!this.sfxVolFunctions || this.sfxVolFunctions.length === 0){
        this.sfxVolFunctions = _.map(sfxVols, function(vol){
            return function(){
                getAudio.changeVol(vol);
                ui.gameProperties.settings.soundVolume = vol;
                ui.gameProperties.settings.sfx = true;
                ui.gameProperties.save();
            };
        })
    }

    this.insetColor = insetColor(!ui.gameProperties.settings.sfx);
    this.drawImageButton(sfxRect, 'mute')
    panelButtons.push([sfxRect, this.sfxFunction]);
    
    _.each(this.sfxVolFunctions, function(volFunction, i){
        var iRect = [sfxRect[0] + 60 * (i + 1), sfxRect[1], sfxRect[2], sfxRect[3]];
        that.insetColor = insetColor(ui.gameProperties.settings.sfx && ui.gameProperties.settings.soundVolume == sfxVols[i]);
        that.drawImageButton(iRect, 'sound' + i);
        that.insetColor = '#000000';
        panelButtons.push([iRect, volFunction]);
    });

    var difficultySetting = ui.world.entityForKey(PARAMETERS.difficultySetting);

    if (!this.difficultyFunctions || this.difficultyFunctions.length === 0){
        this.difficultyFunctions = _.map(_.range(4), function(i){
            return function(){
                difficultySetting.difficultySettingComponent.difficulty = i;
            }
        })
    }

    ctx.fillText('DIFFICULTY', rect[0] + 10, rect[1] + 235)
    _.each(this.difficultyFunctions, (difficultyFunction, i) => {
        var displayIdx = this.difficultyFunctions.length - i;
        var iRect = [rect[0] + 50 + 50 * displayIdx, rect[1] + 220, 40, 40];
        this.insetColor = insetColor(difficultySetting.difficultySettingComponent.difficulty == i);
        this.drawPanelButton(iRect, '' + displayIdx);
        this.insetColor = '#000000';
        panelButtons.push([iRect, difficultyFunction]);
    });

    if (!this.quitFunction){
        this.quitFunction = function(){
            nw.App.quit();
        };
    }

    var quitRect = [rect[0] + 100, rect[1] + 270, 100, 30];
    this.drawPanelButton(quitRect, 'Quit');
    panelButtons.push([quitRect, this.quitFunction]);

    return panelButtons;
};


var singleUseMakeTrain = function(entity, world){
    if (this.trainsToMake > 0){
        this.trainsToMake -= 1;
        return this.baseMakeTrain(entity, world);
    }
    else{
        entity.healthComponent.kill()
        return undefined;
    }
};


var sourceCurrenciesNeededAtPoint = function(){
    return csvIdentifiers.creep;
};


var creepAccept = function(entity, world, creep){
    entity.healthComponent.health -= creep.creepComponent.selfDamage;
    world.makeEntities(creep, this.creepHitEntities, {
        emotion: creep.creepComponent.emotion
    })
};


var focusEfficiency = function(){
    return 1 - (this.focusUsing / (this.focusCount + 2));
};


var sourceExtensionOnClick = function(entity, world, ui){
    var source = world.entityForKey(entity.sourceExtensionComponent.sourceKey);
    source.clickComponent.onClick(source, world, ui);
};


var sourceExtensionRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    if (_.isArray(this.image)){
        this.image = _.sample(this.image);
    }
    var world = ui.world;
    var source = world.entityForKey(entity.sourceExtensionComponent.sourceKey);
    var image = getImage(source.entityManagerComponent.disabled ? this.disabledImage : this.image);

    var xDiff = this.getWobble(lastInterval);
    var x = rect[0] + xDiff;

    ctx.drawImage(image, x, rect[1]);

    var button = world.currentMakingEntity;
    if (button){
        if (button.buttonControlComponent.checkMakePos(world, entity.positionComponent.getAsPos())){
            ctx.drawImage(getImage(this.outlineImage), x, rect[1]);
        }
    }

    if (source.entityManagerComponent.hasNew){
        this.timeToBlink -= lastInterval;
        if (this.timeToBlink > 0){
            ctx.drawImage(getImage(this.newImage), x, rect[1]);
        }
        else if (this.timeToBlink < -0.1){
            this.timeToBlink += 1;
        }
    }
};


var checkParticleEffectAlive = function(entity, world){
    return world.entityForUid(this.sourceId) !== undefined;
};


var spellRender = function(rect, entity, ui){
    if (entity.spellComponent.spellButtonFunction === undefined){
        entity.spellComponent.spellButtonFunction = makeWrappedSpellFunction(entity, ui.world);
    }
    ui.addLine('#spells', {
        line: [
            {
                buttonFunction: entity.spellComponent.spellButtonFunction,
                text: entity.identityComponent.name
            },
            dictString(entity.spellComponent.spellCost)
        ]
    });
};

var makeWrappedSpellFunction = function(entity, world){
    return function(){
        if (world.player.calculatedCurrencyComponent.satisfiesCurrencyReq(entity.spellComponent.spellCost)){
            world.currentSpell = entity;
        }
    };
};


var starParticleEffectRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    if (this.timeDrawn > this.scaleTime){
        entity.healthComponent.kill();
    }

    this.timeDrawn += lastInterval;
    var dist = (this.timeDrawn / (3 * this.scaleTime)) + 1;
    var image = getImage(this.image);

    for (var i = 8; i < 11; i++){
        var angle = i * Math.PI / 6;
        var x = 60 * dist * Math.cos(angle);
        var y = 60 * dist * Math.sin(angle);
        ctx.drawImage(image, this.effectPos[0] + x, this.effectPos[1] + y);
    }
};


var checkSteamAchievement = function(entity, args){
    if (args.key == entity.steamAchievementComponent.keyToCheck){
        console.log('Completed achievement: ' + entity.steamAchievementComponent.steamAchievementKey);
        if (STEAM){
            greenworks.activateAchievement(entity.steamAchievementComponent.steamAchievementKey, trueFunction);
        }
    }
};


var swallowOpinionCreate = function(entity, world){
    entity.swallowOpinionComponent.previousOpinions = _.chain(world.entities)
        .filter((worldEntity) => {
            return (worldEntity.opinionComponent &&
                    worldEntity.personHeldComponent.personHeld == entity.personHeldComponent.personHeld &&
                    worldEntity.identityComponent.key != entity.identityComponent.key &&
                    !_.contains(this.swallowExceptions, worldEntity.identityComponent.key));
        })
        .map(function(worldEntity){
            worldEntity.healthComponent.kill();
            return worldEntity.identityComponent.key;
        })
        .value();
};


var killPersonGoals = function(entity, world){
    _.chain(world.entities)
        .filter(function(worldEntity){ 
            return worldEntity.personHeldComponent &&
                worldEntity.worldGoalComponent;
        })
        .each(function(worldEntity){
            worldEntity.healthComponent.kill();
        });
};


var swallowOpinionKill = function(entity, world){
    world.makeEntities(entity, entity.swallowOpinionComponent.previousOpinions);
};


var checkTickContinue = function(world, entity){
    return !(this.currency.will && 
             this.currency.will < 0 && 
             world.player.calculatedCurrencyComponent.currencies.will < 0);
};


var timedDeathRender = function(rect, entity){
    if (entity.timedDeathComponent){
        ctx.fillStyle = '#ffffff';
        var timeLeft = Math.floor(entity.timedDeathComponent.timeToDeath / PARAMETERS.ticksPerTick[0]);
        ctx.fillText('' + timeLeft, rect[0] + rect[2] / 2 - 15, rect[1] + rect[3] - 2);
    }
};


var timedDifficultyModify = function(entity, world){
    var difficulty = world.entityForKey(PARAMETERS.difficultySetting).difficultySettingComponent.difficulty;
    entity.timedDeathComponent.timeToDeath += Math.floor(difficulty * entity.timedDeathComponent.timeToDeath / 3);
};


var timedMakeEntities = function(entity, world){
    if (entity.timedDeathComponent && entity.timedDeathComponent.timeToDeath <= 0){
        world.makeEntities(entity, this.timedOutEntities);
    }
};


var timedRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    if (this.timeDrawn < this.renderTime){
        this.timeDrawn += lastInterval;
        this.renderWhileTimed(rect, entity, ui, rectCount, timeToNextTick, lastInterval);
    }
    else{
        var idx = entity.rendererComponents.indexOf(this);
        if (this.nextRenderComponent){
            entity.rendererComponents[idx] = this.nextRenderComponent;
        }
        else{
            entity.rendererComponents.splice(idx, 1);
        }
    }
};


var timedScaleRenderWhenTimed = function(rect, entity){
    var img = getImage(this.image);
    var scale = 1 + 0.5 * (this.renderTime - this.timeDrawn) / this.renderTime;
    var newRect = [rect[0] - rect[2] * (scale - 1) / 2, rect[1] - rect[3] * (scale - 1) / 2, rect[2] * scale, rect[3] * scale];
    ctx.drawImage(img, newRect[0], newRect[1], newRect[2], newRect[3]);
};


var toastRender = function(rect, entity, ui, rectCount, timeToNextTick){
    ctx.fillStyle = '#ff0000';
    ctx.beginPath();
    ctx.arc(rect[0], rect[1], 5, 0, 2 * Math.PI);
    ctx.fill();
};


var toastClick = function(entity, world, ui){
    ui.openPanelAtPos(this.panelKey, entity.positionComponent);
    entity.healthComponent.health = 0;
};


var toastNotification = function(entity, args){
    if (args.key == entity.clickComponent.panelKey){
        entity.healthComponent.health = 0;
    }
};


var damageRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    if (entity.damageComponent){
        if (entity.damageComponent.ticks <= 0){
            ctx.fillStyle = '#ffffff';
        }
        else{
            ctx.fillStyle = '#ffff00';
            var cooldownImage = getImage('cooldown' + min(entity.damageComponent.ticks, 2));
            ctx.drawImage(cooldownImage, rect[0], rect[1]);
        }

        if (entity.damageComponent.charges === 0){
            this.blinkTimer += lastInterval
        }
        else{
            this.blinkTimer = 0;
        }

        if (this.blinkTimer < 1.8){
            var i;

            var width = (entity.damageComponent.maxCharges == 5) ? 4 : 6;

            for (i = 0; i < entity.damageComponent.charges; i++){
                ctx.fillRect(rect[0] + 17 + (width * 2 - 2) * i, rect[1] + rect[3] - 12, width, 10);
            }
            
            ctx.fillStyle = '#aaaaaa';
            for (; i < entity.damageComponent.maxCharges; i++){
                ctx.fillRect(rect[0] + 17 + (width * 2 - 2) * i, rect[1] + rect[3] - 12, width, 10);
            }
        }
        else if (this.blinkTimer > 2){
            this.blinkTimer -= 2;
        }
    }
};


var towerTick = function(entity){
    _.each(entity.rendererComponents, function(renderComponent){
        if (renderComponent.activated > 0){
            renderComponent.activated -= 1;
        }
    });
        
    this.timeToAttrition -= 1;

    if (this.timeToAttrition <= 0){
        this.timeToAttrition = PARAMETERS.dayTicks;
        if (this.damageComponent){
            this.damageComponent.charges -= 1;
            if (this.damageComponent.charges < 0){
                this.damageComponent.charges = 0;
            }
        }
    }
};


var currencyActionForTower = function(entity, world){
    return _.chain(world.entities)
        .filter(function(chkEntity){
            return chkEntity.currencyActionComponent && 
                _.contains(chkEntity.currencyActionComponent.towerIds, entity.uid);
        })
        .first()
        .value();
};

var possibleCurrencyActionForTower = function(entity, world){
    return _.chain(world.entities)
        .filter(function(chkEntity){
            return chkEntity.currencyActionComponent &&
                chkEntity.currencyActionComponent.towerKey == entity.identityComponent.key;
        })
        .first()
        .value();
};

var canDamageTower = function(entity){
    return entity.damageComponent === undefined;
};


var towerCreateExec = function(entity, world){
    _.chain(world.entities)
        .filter(function(worldEntity){
            return worldEntity.currencyActionComponent &&
                worldEntity.currencyActionComponent.towerKey == entity.identityComponent.key &&
                worldEntity.currencyActionComponent.towerIds.length > 0;
        })
        .each(function(worldEntity){
            worldEntity.currencyActionComponent.nextIds.push(entity.identityComponent.uid);
        });
};


var towerCurrenciesNeededAtPoint = function(entity, world){
    var currencyAction = entity.towerComponent.possibleCurrencyActionForTower(entity, world);

    return currencyAction ? currencyAction.currencyActionComponent.currenciesRequired : [];
};

var towerCurrenciesMadeAtPoint = function(entity, world){
    var currencyAction = entity.towerComponent.possibleCurrencyActionForTower(entity, world);

    return currencyAction ? currencyAction.currencyActionComponent.currenciesToMake : [];
};

var towerInteractWithTrain = function(entity, world, train){
    var currencyAction = entity.towerComponent.possibleCurrencyActionForTower(entity, world);
    var trainPath = world.entityForUid(train.trainVelocityComponent.trainPath);

    if (!currencyAction){
        train.healthComponent.kill();
        if (trainPath){
            trainPath.healthComponent.kill();
        }
        return false;
    }

    var entitiesToLoad = [];

    var currenciesNeeded = train.trainComponent.calculateTrainCurrencies(train, world, 1);

    var isLoading = _.any(this.currenciesMadeAtPoint(entity, world), function(currencyKey){
        return currenciesNeeded[currencyKey] && (currenciesNeeded[currencyKey] > 0);
    });

    if (isLoading && this.currenciesExistAtPoint(entity, world)){
        var currenciesMadeAtPoint = _.countBy(this.currenciesMadeAtPoint(entity, world), _.identity);

        var currenciesAtPoint = _.filter(world.entities, function(worldEntity){
            return worldEntity.pathVelocityComponent &&
                worldEntity.positionComponent.comparePos(entity.positionComponent);
        });

        entitiesToLoad = _.filter(currenciesAtPoint, function(currency){
            var key = currency.identityComponent.key;
            if (currenciesMadeAtPoint[key] && currenciesMadeAtPoint[key] > 0){
                currenciesMadeAtPoint[key] -= 1;
                return true;
            }
            return false;
        });
    }
    else{
        if (entity.focusTickResponseComponent.isReady || entity.damageComponent){
            var currenciesToConsume = this.currenciesNeededAtPoint(entity, world);

            var currencyCounts = _.countBy(currenciesToConsume, _.identity);
            
            
            var carriage = train.trainComponent.getCurrentCarriage(train, world);
            carriage.carriageComponent.holding = _.filter(carriage.carriageComponent.holding, function(holdId){
                var holdItem = world.entityForUid(holdId);
                if (!holdItem){
                    return false;
                }
                if (currencyCounts[holdItem.identityComponent.key] > 0){
                    currencyCounts[holdItem.identityComponent.key] -= 1;
                    holdItem.healthComponent.kill();
                    return false;
                }
                return true;
            });
            

            currencyAction = entity.towerComponent.currencyActionForTower(entity, world);
            
            if (!currencyAction){
                train.healthComponent.kill();
                var trainPath = world.entityForUid(train.trainVelocityComponent.trainPath);
                if (trainPath){
                    trainPath.healthComponent.kill();
                }
                return false;
            }
            
            entitiesToLoad = world.makeEntities(entity, currencyAction.currencyActionComponent.currenciesToMake);
            if (currencyAction.currencyActionComponent.entitiesOnConvert){
                world.makeEntities(entity, currencyAction.currencyActionComponent.entitiesOnConvert);
            }
                        
            
            if (entity.damageComponent && entity.damageComponent.charges < entity.damageComponent.maxCharges){
                entity.damageComponent.charges += 1;
                if (entity.damageComponent.charges >= entity.damageComponent.maxCharges){
                    trainPath.healthComponent.kill();
                    train.healthComponent.kill();
                }
            }

            _.each(this.convertExecutables, function(convertExecutable){
                convertExecutable.execute(entity, world);
            });

            var rect =  world.hexMap.cellForPosComp(entity.positionComponent).rect;
            world.makeEntities(entity, this.risingTextKey, {
                text: currencyAction.identityComponent.name,
                effectPos: [rect[0], rect[1]]
            })
            
            entity.focusTickResponseComponent.isReady = false;
        }
        else{
            entity.focusTickResponseComponent.isWaiting = true;
            return true;
        }
    }
    
    if (entitiesToLoad.length > 0){
        var carriage = train.trainComponent.getCurrentCarriage(train, world);
        _.each(entitiesToLoad, function(entityToLoad){
            carriage.carriageComponent.holding.push(getUid(entityToLoad));
        });
    }

    entity.managedComponent.addExperienceToManager(entity, world);

    return false;
};


var towerFocusTick = function(entity, world){
    if (this.isWaiting){
        this.isReady = true;
        this.isWaiting = false;
        world.makeEntities(entity, PARAMETERS.towerFlash);
    }
};


var canBuildTower = function(world){
    var managingEntity = world.entityForKey(this.managerKey);
    
    if (!managingEntity){
        return false;
    }

    if (!managingEntity.entityManagerComponent.canMakeManagedEntity(managingEntity, world)){
        return false;
    }

    var existingTowerCount = _.reduce(this.limitingKeys, function(memo, val){
        var existingTowers = world.entitiesForKey(val);
        existingTowers = _.filter(existingTowers, 
                                  existingTower => _.intersection(existingTower.interactionComponent.interactionKeys, 
                                                                  managingEntity.interactionComponent.interactionKeys).length > 0);
        return memo + existingTowers.length;
    }, 0);

    if (existingTowerCount >= this.maxTowerCount){
        return false;
    }

    return true;
};

var buildTower = function(world, pos, entity, interactionKey){
    entity.buttonControlComponent.isNew = false;
    var manager = world.entityForKey(this.managerKey);
    if (manager){
        manager.entityManagerComponent.checkForNewManaged(manager, world);
    }
    
    var args = {
        x: pos[0],
        y: pos[1],
        sourceKey: this.managerKey,
        sourceTrait: this.managerKey
    };
    if (interactionKey){
        args.interactionKeys = [interactionKey];
    }

    var entity = makeEcs(this.constructionKey, args);

    return entity;
};

var checkTowerMakePos = function(world, pos){
    var that = this;
    var isManagedPos = _.any(world.entities, function(entity){
        return (entity.identityComponent && entity.identityComponent.key == that.managerKey ||
                (entity.sourceExtensionComponent && entity.sourceExtensionComponent.sourceKey == that.managerKey)) &&
            entity.positionComponent.isPos(pos);
    });

    var isFreePos = !_.any(world.entities, function(entity){
        return entity.managedComponent && entity.positionComponent.isPos(pos);
    });
    
    return isManagedPos && isFreePos;
};


var killTowerButtonExec = function(entity, world){
    if (this.killTowersOnDeath){
        _.each(world.entitiesForKey(entity.buttonControlComponent.entityKey), function(tower){
            tower.healthComponent.health = 0;
        });
    }
};


var towerFlashRender = function(rect, entity, ui, rectCount, timeToDeath, lastInterval){
    if (this.timeToDeath <= 0){
        entity.healthComponent.kill();
    }
    else{
        var imageIndex = Math.floor(this.timeToDeath / 0.167);
        var image = getImage(this.images[imageIndex]);
        ctx.drawImage(image, rect[0], rect[1]);
        
        this.timeToDeath -= lastInterval;
    }
};


var getTowerForTickModifierRender = function(entity, world){
    return world.entityForUid(entity.tickModifyComponent.towerId);
};

var towerTickModifierRender = function(rect, entity, ui){
    var tower = this.getTowerForTickModifierRender(entity, ui.world);
    if (!tower){
        return;
    }

    var rect = ui.world.hexMap.cellForPosComp(tower.positionComponent).rect;
    ctx.drawImage(getImage(this.image), rect[0] + rect[2] / 2, rect[1] + rect[3] / 2);
};


var calculateTrainCurrencies = function(entity, world, offset){
    var trainPath = world.entityForUid(entity.trainVelocityComponent.trainPath);
    if (!trainPath){
        return {};
    }
    var currencies = [];
    offset = offset ? offset : 0;
    var startIndex = entity.trainVelocityComponent.stopIndex + offset;
    for (var i = startIndex; i < trainPath.trainPathComponent.trainStops.length; i++){
        var trainStop = trainPath.trainPathComponent.trainStops[i];
        var stop = world.entityForUid(trainStop);
        if (stop){
            currencies = currencies.concat(stop.focusPathStopComponent.currenciesNeededAtPoint(stop, world));
        }
    }
    
    return _.countBy(currencies, _.identity);
};

var getCurrentCarriage = function(entity, world){
    return world.entityForUid(this.carriage);
};

var getCurrenciesHeld = function(entity, world){
    var carriage = world.entityForUid(this.carriage);

    return _.chain(carriage.carriageComponent.holding)
        .map(function(currencyId){ return world.entityForUid(currencyId); })
        .filter(_.identity)
        .value();
};


var trainCreateExec = function(entity, world){
    var startEntity = world.entityForUid(this.trainStartId);

    if (!startEntity){
        entity.healthComponent.health = 0;
        return;
    }

    entity.positionComponent.x = startEntity.positionComponent.x;
    entity.positionComponent.y = startEntity.positionComponent.y;
    
    var trainPath = world.entityForUid(this.trainPath);
    var start = [entity.positionComponent.x, entity.positionComponent.y];


    var stops = _.map(trainPath.trainPathComponent.trainStops, path => _.clone(path));
    entity.trainVelocityComponent.trainStops = stops;
    
    entity.trainVelocityComponent.currentPath = entity.trainVelocityComponent.allPaths[0];
    
    var carriage = world.makeEntities(entity, this.carriageKey, {
        x: entity.positionComponent.x,
        y: entity.positionComponent.y,
    })[0];
                
    entity.trainComponent.carriage = carriage.uid;

    var existingCarriages = _.filter(world.entities, worldEntity => worldEntity.carriageComponent);

    _.each(entity.trainComponent.calculateTrainCurrencies(entity, world), function(val, key){
        var startCurrency = _.chain(world.entities)
            .filter(function(entity){
                return entity.identityComponent && 
                    entity.identityComponent.key == key &&
                    entity.positionComponent.comparePos(startEntity.positionComponent) &&
                    !_.any(existingCarriages, carriage => _.contains(carriage.carriageComponent.holding, entity.uid));
            })
            .first()
            .value();

        if (startCurrency){
            carriage.carriageComponent.holding.push(startCurrency.uid);
        }
    });
};


var trainKillExec = function(entity, world){
    var carriage = world.entityForUid(entity.trainComponent.carriage);
        
    carriage.healthComponent.kill();

    _.chain(carriage.carriageComponent.holding)
        .map(function(holdingId){ return world.entityForUid(holdingId); })
        .each(function(currency){
            if (currency){
                currency.healthComponent.health = 0;
            }
        });
};


var trainRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    var dx = 15;
    var dy = 15;
    
    if (ui.game.tickSystemWrapper.running){
        var subfraction = 1 - (timeToNextTick * ui.game.ticksPerTick * TICKS_PER_SECOND);
        var fraction = (ui.game.tickSystemWrapper.ticks + subfraction) / 10;

        if (ui.world.lostControl()){
            fraction = 0;
        }
        
        var nextPos = entity.trainVelocityComponent.currentPath[0];
        var drawPos = [rect[0], rect[1]];
        if (nextPos){
            nextPos = ui.world.hexMap.hexMap[nextPos[0]][nextPos[1]].rect;
            drawPos = [drawPos[0] + (nextPos[0] - drawPos[0]) * fraction, 
                       drawPos[1] + (nextPos[1] - drawPos[1]) * fraction];
        }

        _.each(entity.trainComponent.getCurrenciesHeld(entity, ui.world), function(currency, i){
            _.each(currency.rendererComponents, function(renderComponent){
                renderComponent.renderPos = [drawPos[0] + rect[2] / 2 - 8,
                                             drawPos[1] + rect[3] / 2 - 8 + i * 10];
            });
        });
        
        drawPos = [drawPos[0] + rect[2] / 2 - dx, drawPos[1] + rect[3] / 2 - dy];
        drawPos = [Math.floor(drawPos[0]), Math.floor(drawPos[1])];
        
        this.timeDrawn += lastInterval;
        var chkPreviousPos = (pos) => (!_.any(this.previousPositions, (prevPos) => comparePos(prevPos, pos)));
        if (this.timeDrawn > 0.1){
            if (chkPreviousPos(drawPos)){
                this.previousPositions.push(drawPos);
            }
            else{
                var nextPos = [drawPos[0], drawPos[1] - 10];
                if (!chkPreviousPos(nextPos)){
                    nextPos = [drawPos[0] - 10, drawPos[1] + 10];
                    if (!chkPreviousPos(nextPos)){
                        nextPos = [drawPos[0] + 10, drawPos[1] + 10];
                    }
                }
                this.previousPositions.push(nextPos);
            }
            this.linePos = drawPos;
            this.previousPositions = _.last(this.previousPositions, 3);
            
            this.timeDrawn = 0;
        }
    }
        
    ctx.fillStyle = '#ede9e6';
    ctx.strokeStyle = this.trainColor;
    
    _.each(this.previousPositions, function(prevPos){
        ctx.beginPath();
        ctx.arc(prevPos[0] + 15, prevPos[1] + 15, 3, 0, 2 * Math.PI);
        ctx.fill();
        
        ctx.beginPath();
        ctx.arc(prevPos[0] + 15, prevPos[1] + 15, 4, 0, 2 * Math.PI);
        ctx.stroke();
    });
};


var trainPanelRender = function(rect, entity, ui){
    var panelButtons = [];

    var world = ui.world;
    var path = world.entityForUid(entity.panelComponent.sourceId);
    
    if (!path){
        entity.panelComponent.closed = true;
    }
    else if (entity.panelComponent.minimized){
        entity.panelComponent.closed = true;
    }
    else {
        entity.absolutePositionComponent.size[1] = 350 + path.trainPathComponent.trainStops.length * 60;
        rect = entity.absolutePositionComponent.getAbsPosRect();
        
        ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);

        ctx.fillStyle = this.textColor;
        ctx.font = getFont(28);

        var header = 'Train';

        var width = ctx.measureText(header).width;
        ctx.fillText(header, rect[0] + (rect[2] - width) / 2, rect[1] + 60);

        panelButtons.push(this.drawHelpButton(entity, rect));

        ctx.font = getFont(20);
        ctx.fillSyle = this.textColor;
        var y;
        _.each(path.trainPathComponent.trainStops, (stopId, i) => {
            if (i !== 0){
                ctx.drawImage(getImage(this.arrowImage), rect[0] + 120, y + 10);
            }

            y = rect[1] + 100 + (i + 1) * 60;

            var stop = world.entityForUid(stopId);

            if (!stop){
                return;
            }

            if (stop.uiDataComponent){
                stop.uiDataComponent.drawImage(stop, world, [rect[0] + 30, y - 30]);
            }
            else if (stop.identityComponent && stop.identityComponent.image){
                ctx.drawImage(getImage(stop.identityComponent.image), rect[0] + 30, y - 30);
            }
            
            ctx.fillText(stop.identityComponent.name, rect[0] + 100, y);
        });

        ctx.font = getFont(18);
        var buttonWidth = 200;

        if (!path.trainPathComponent.canMakeTrain(path, world) && PARAMETERS.buildablePaths){
            var connectRect = [rect[0] + (rect[2] - buttonWidth) / 2, y + 40, buttonWidth, 40];
            this.drawPanelButton(connectRect, 'CONNECT TRACK');
            panelButtons.push([connectRect, trainPanelStartTrack]);
        }
        else if (this.isConnectingTrack){
            entity.panelComponent.closed = true;
        }

        var killPathRect = [rect[0] + (rect[2] - buttonWidth) / 2, y + 100, buttonWidth, 40];
        this.drawPanelButton(killPathRect, 'STOP TRAIN');
        panelButtons.push([killPathRect, path.trainPathComponent.getKillTrainPath(path, world)]);

        ctx.font = BASE_FONT;
    }
    return panelButtons;
};

var trainPanelStartTrack = function(entity, ui){
    entity.rendererComponents[0].isConnectingTrack = true;
    entityPanelStartTrack(entity, ui);
};


var makeTrain = function(entity, world){
    if (this.allPaths.length === 0){
        var startEntity = world.entityForUid(this.trainStartId);

        if (!startEntity){
            entity.healthComponent.kill();
            return;
        }
        
        var start = [startEntity.positionComponent.x, startEntity.positionComponent.y];

        var paths = _.filter(world.entities, function(worldEntity){
            return worldEntity.pathMoveComponent;
        });

        _.each(this.trainStops, (stopId) => {
            var stop = world.entityForUid(stopId);
            stop = [stop.positionComponent.x, stop.positionComponent.y];
            var path = world.ensurePath(start, stop, paths);
            this.allPaths.push(path);
            start = stop;
        });
    }

    var train = makeEcs(this.trainKey, {
        trainPath: entity.uid,
        trainStartId: this.trainStartId,
        trainColor: this.trainColor,
        allPaths: _.map(this.allPaths, path => _.clone(path))
    });
    entity.trainPathComponent.trainId = train.uid;
    world.addEntity(train);
    return train;
};

var canMakeTrain = function(entity, world){
    if (this.requiresFullPath){
        var paths = world.getAllPaths();
        
        var stops = [this.trainStartId].concat(this.trainStops);
        
        stops = _.map(stops, function(stopId){ return world.entityForUid(stopId); });
        
        if (_.any(stops, function(stop){ return stop === undefined; })){
            return false;
        }

        var getPosition = function(entity){
            return [entity.positionComponent.x, entity.positionComponent.y];
        };
        for (var i = 0; i < stops.length - 1; i++){
            var start = getPosition(stops[i]);
            var stop = getPosition(stops[i + 1]);
            if (determinePath(start, stop, paths) === undefined){
                return false;
            }
        }
    }
    return true;
};

var getKillTrainPath = function(entity, world){
    if (this.killTrainPath === undefined){
        this.killTrainPath = function(){
            entity.healthComponent.kill();
            _.chain(world.entities)
                .filter(function(worldEntity){
                    return worldEntity.trainVelocityComponent &&
                        worldEntity.trainVelocityComponent.trainPath == entity.uid;
                })
                .each(function(worldEntity){
                    worldEntity.healthComponent.kill();
                });
        };
    }
    return this.killTrainPath;
};


var trainPathWarningRender = function(rect, entity, ui){
    var world = ui.world;

    if (!entity.trainPathComponent.canMakeTrain(entity, world)){
        var lastStop = world.entityForUid(_.last(entity.trainPathComponent.trainStops));
        
        if (!lastStop){
            return;
        }

        var lastStopRect = world.hexMap.cellForPosComp(lastStop.positionComponent).rect;
        ctx.fillStyle = '#ffff00';
        ctx.fillRect(lastStopRect[0] + lastStopRect[2] - 10, lastStopRect[1] + lastStopRect[3], 10, 10);
    }
};


var trainPathRenderWrapper = function(entity, world, fn){
    _.each(entity.trainPathComponent.trainStops, function(stopId){
        var trainStop = world.entityForUid(stopId);
        if (!trainStop){
            return;
        }

        fn(trainStop)
    });
};

var trainPathTowerActiveRender = function(rect, entity, ui){
    trainPathRenderWrapper(entity, ui.world, (trainStop) => {
        if (trainStop.rendererComponents[0].activeImage){
            trainStop.rendererComponents[0].isActive = true;
        }
    });
};


var trainPathSecondRender = function(rect, entity, ui){
    trainPathRenderWrapper(entity, ui.world, (trainStop) => {
        trainStop.rendererComponents[0].outlineImage = this.image;
        trainStop.rendererComponents[0].trainColor = entity.trainPathComponent.trainColor;
    });
};


var trainPathMenuCloseExec = function(entity, world){
    if (!PARAMETERS.buildablePaths && !entity.trainPathComponent.canMakeTrain(entity, world)){
        entity.healthComponent.kill();
        var lastStop = _.last(entity.trainPathComponent.trainStops);
        lastStop = world.entityForUid(lastStop);
        if (lastStop.constructionComponent && !lastStop.constructionComponent.ticked){
            lastStop.healthComponent.kill();
        }
    }
};


var blinkTrainStopsExec = function(entity, world){
    trainPathRenderWrapper(entity, world, (trainStop) => {
        var renderComponent = trainStop.rendererComponents[0];
        renderComponent.blinkingOutlineImage = this.image;
        renderComponent.blinkingOutlineTime = 1;
    });
};


var setTrainColorExec = function(entity, world){
    var existingPathColors = _.chain(world.entities)
        .filter(worldEntity => (worldEntity.trainPathComponent &&
                                worldEntity.trainPathComponent.stoppable &&
                                _.isEqual(worldEntity.interactionComponent.interactionKeys,
                                          entity.interactionComponent.interactionKeys)))
        .map(trainPath => trainPath.trainPathComponent.trainColor)
        .value();
    var possibleColors = [
        '#00ccff',
        '#ff9900',
        '#9900cc',
        '#00ff99',
        '#00ffff',
        '#00ffaa',
        '#0099ff'
    ];

    var color = _.chain(possibleColors)
        .filter(possibleColor => !_.contains(existingPathColors, possibleColor))
        .first()
        .value();

    color = color ? color : '#ffffff';

    entity.trainPathComponent.trainColor = color;
};


var trainPathCircRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    if (entity.trainPathComponent.allPaths.length === 0){
        return;
    }

    var startEntity = ui.world.entityForUid(entity.trainPathComponent.trainStartId);

    if (this.renderTime < 100){
        this.renderTime += lastInterval;
    }

    var paths = [];

    _.each(entity.trainPathComponent.allPaths, function(newPath){
        paths = paths.concat(newPath);
    });

    ctx.strokeStyle = entity.trainPathComponent.trainColor
    ctx.beginPath();
    
    var startPos;
    
    if (startEntity && startEntity.focusMakingComponent){
        var cellRect = ui.world.hexMap.cellForPosComp(startEntity.positionComponent).rect;
        startPos = [cellRect[0] + cellRect[2] / 2, cellRect[1] + cellRect[3] / 2];
        ctx.moveTo(startPos[0], startPos[1]);
    }
    
    _.each(paths, (pos, i) => {
        if (i < this.renderTime * 8){
            var cellRect = ui.world.hexMap.cellForPosComp({x: pos[0], y: pos[1]}).rect;
            var pos = [cellRect[0] + cellRect[2] / 2, cellRect[1] + cellRect[3] / 2];

            if (i === 0 && !startEntity){
                ctx.moveTo(pos[0], pos[1]);
            }
            else{
                ctx.lineTo(pos[0], pos[1]);
            }
        }
    });

    ctx.stroke();
};


var getPathCurrenciesNeeded = function(entity, world){
    if (!this.memoizedCurrenciesNeeded){
        var currenciesNeeded = {};
        
        var stayAlive = _.all(this.stops, function(stopId){
            var stop = world.entityForUid(stopId);
            if (!stop){
                return false;
            }
            stop.focusPathStopComponent.updateCurrencyDictWithStop(stop, world, currenciesNeeded);
            return true;
        });

        if (!stayAlive){
            return {};
        }

        currenciesNeeded = _.chain(currenciesNeeded)
            .map(function(val, key){
                return [key, val];
            })
            .filter(function(pair){
                return pair[1] > 0;
            })
            .value();
        
        this.memoizedCurrenciesNeeded = dictFromPairs(currenciesNeeded);
    }
    return this.memoizedCurrenciesNeeded;
};

var makeTrainPath = function(entity, world){
    entity.healthComponent.kill();

    this.stops.reverse();

    if (this.stops.length > 0){
        var selfEntity = world.entityForKey(PARAMETERS.self);
        var trainPath = makeEcs(this.trainPathKey, {
            trainStartId: selfEntity.uid,
            trainStops: this.stops,
        });
        world.addEntity(trainPath);

        if (!trainPath.trainPathComponent.canMakeTrain(trainPath, world)){
            var panel = makeEcs(PARAMETERS.trainPanel, {
                key: 'trainPath_' + trainPath.uid,
                sourceId: trainPath.uid
            });
            panel.panelComponent.expandPanel();
            world.addEntity(panel);
        }

        _.each(world.entitiesForKey(PARAMETERS.trainPathMakingPanel), function(panel){
            panel.panelComponent.closed = true;
        })
    }
};

var addTrainStop = function(entity, world, stop){
    if (this.checkTrainStop(entity, world, stop)){
        if (stop.identityComponent.key == PARAMETERS.self){
            this.makeTrainPath(entity, world);
            
            if (world.gameProperties.settings.sfx){
                var sound = getAudio.get('sounds/beep.ogg');
                sound.play();
            }
        }
        else{
            this.stops.push(stop.uid);
            if (this.stops.length > 1){
                stop.rendererComponents[0].timeDrawn = 0;

                if (world.gameProperties.settings.sfx){
                    var sound = getAudio.get('sounds/beep.ogg');
                    sound.play();
                }
            }
        }
    }
    this.memoizedCurrenciesNeeded = undefined;
};

var isRelevantFocusStop = function(worldEntity, world, pathCurrenciesNeeded){
    return worldEntity.focusPathStopComponent &&
        _.any(worldEntity.focusPathStopComponent.currenciesMadeAtPoint(worldEntity, world), 
              function(key){ return pathCurrenciesNeeded[key]; });
};

var isFreeStop = function(worldEntity, world){
    return !_.any(world.entities, function(chkEntity){
        return chkEntity.trainPathComponent && _.contains(chkEntity.trainPathComponent.trainStops, worldEntity.uid);
    });
};

var checkTrainStop = function(entity, world, worldEntity){
    if (worldEntity.focusPathStopComponent && worldEntity.focusPathStopComponent.isDisabled){
        return false;
    }
    
    if (this.stops.length > 0){
        var pathCurrenciesNeeded = this.getPathCurrenciesNeeded(entity, world);

        if (Object.keys(pathCurrenciesNeeded).length > 0){
            return isRelevantFocusStop(worldEntity, world, pathCurrenciesNeeded) &&
                worldEntity.interactionComponent.canInteractSelf() &&
                !_.contains(this.stops, worldEntity.uid) &&
                isFreeStop(worldEntity, world);
        }
        else{
            return worldEntity.identityComponent && worldEntity.identityComponent.key == PARAMETERS.self;
        }
    }
    else{
        return worldEntity.focusPathStopComponent && 
            worldEntity.focusPathStopComponent.isPathEnd &&
            isFreeStop(worldEntity, world);
    }
};

var removeTrainStop = function(index){
    this.stops.splice(index, 1);
    this.memoizedCurrenciesNeeded = undefined;
};


var trainPathMakerHighlightRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    var world = ui.world;
    
    this.timeToBlink -= lastInterval;
    
    if (this.timeToBlink > 0){
        var highlightEntities = _.filter(world.entities, function(worldEntity){
            return entity.trainPathMakerComponent.checkTrainStop(entity, world, worldEntity);
        });

        _.each(highlightEntities, (worldEntity) => {
            var rect = world.hexMap.cellForPosComp(worldEntity.positionComponent).rect;
            ctx.drawImage(getImage(this.highlightImage), rect[0], rect[1]);
        });
    }
    else if (this.timeToBlink < -0.1){
        this.timeToBlink += 1;
    }
};


var trainPathMakerKill = function(entity, world){
    _.chain(world.entities)
        .filter(function(entity){
            return entity.identityComponent &&
                entity.identityComponent.key == '#trainPathMaker';
        })
        .each(function(entity){
            entity.panelComponent.closed = true;
        });
};


var trainPathMakerTick = function(entity, world){
    entity.trainPathMakerComponent.memoizedCurrenciesNeeded = undefined;
};


var trainPathMakingPanelRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    entity.panelComponent.panelButtons = [];
        
    var panelButtons = [];
    var world = ui.world;
    
    var trainPathMaker = _.chain(world.entities)
        .filter(function(worldEntity){
            return worldEntity.trainPathMakerComponent;
        })
        .first()
        .value();

    if (!trainPathMaker){
        this.completeFunction = undefined;
        this.killStopFunctions = [];
        return;
    }

    if (entity.panelComponent.closed){
        return;
    }

    var stops = trainPathMaker.trainPathMakerComponent.stops;

    if (stops.length > 0){
        entity.panelComponent.sourceId = stops[0]
    }

    entity.absolutePositionComponent.size[1] = 350 + stops.length * 60;
    rect = entity.absolutePositionComponent.getAbsPosRect();

    ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);

    ctx.fillStyle = this.textColor;
    ctx.textAlign = 'center';
    ctx.font = getFont(28);
    ctx.fillText('New Train', rect[0] + rect[2] / 2, rect[1] + 40);
    ctx.textAlign = 'left';


    ctx.font = getFont(20);
    var currenciesNeeded = trainPathMaker.trainPathMakerComponent.getPathCurrenciesNeeded(trainPathMaker, world);
    currenciesNeeded = _.chain(currenciesNeeded)
        .map(function(val, key){
            return [key, val];
        })
        .filter(function(pair){
            return pair[1] > 0;
        })
        .value();
    
    var y = rect[1] + 100;
    
    if (currenciesNeeded.length > 0){
        var currencySt = 'Currencies Needed:';
        ctx.fillText(currencySt, rect[0] + 20, y);
        ctx.font = getFont(18);
        _.each(currenciesNeeded, function(pair){
            y += 30;
            var st = getSaneName(pair[0]);
            if (pair[1] > 1){
                st += ' - ' + pair[1];
            }

            for (var i = 0; i < pair[1]; i++){
                ctx.drawImage(getImage('currency'), rect[0] + 20 + 5 * i, y - 9);
            }

            ctx.fillText(st, rect[0] + 50, y + 5);
        });
        ctx.font = getFont(20);

        ctx.strokeStyle = this.insetColor;
        ctxRoundedRect([rect[0] + 10, rect[1] + 110, rect[2] - 20, y - rect[1] - 85]);
    }
    
    y += 60;
    ctx.fillText('Path Taken:', rect[0] + 20, y)
    y += 50;
    
    var startStopsY = y - 40;

    for (var i = stops.length - 1; i >= 0; i--){
        var stop = world.entityForUid(stops[i]);

        if (!stop){
            break;
        }
        
        if (this.killStopFunctions[i] === undefined){
            this.killStopFunctions[i] = function(){
                trainPathMaker.trainPathMakerComponent.removeTrainStop(i);
            };
        }

        if (stop.uiDataComponent){
            stop.uiDataComponent.drawImage(stop, world, [rect[0] + 10, y - 30]);
        }
        if (stop.identityComponent && stop.identityComponent.image){
            ctx.drawImage(getImage(stop.identityComponent.image), rect[0] + 10, y - 30);
        }

        ctx.fillStyle = this.textColor;
        if (stop.identityComponent.name.length > 20){
            ctx.font = getFont(14);
            ctx.fillText(stop.identityComponent.name, rect[0] + 80, y);
            ctx.font = getFont(20);
        }
        else{
            ctx.fillText(stop.identityComponent.name, rect[0] + 80, y);
        }
        
        if (stops.length > 1 && i == stops.length - 1){
            var removeRect = [rect[0] + rect[2] - 55, y - 20, 40, 40];
            this.drawPanelButton(removeRect, 'X');
            panelButtons.push([removeRect, this.killStopFunctions[i]]);
        }

        y += 60;
    };

    ctxRoundedRect([rect[0] + 10, startStopsY, rect[2] - 20, y - startStopsY - 10]);

    if (stops.length > 0 && Object.keys(currenciesNeeded).length == 0){
        if (this.completeFunction === undefined){
            this.completeFunction = function(){
                trainPathMaker.trainPathMakerComponent.makeTrainPath(trainPathMaker, world);
                if (world.gameProperties.settings.sfx){
                    var sound = getAudio.get('sounds/beep.ogg');
                    sound.play();
                }
            };
        }

        this.blinkTime -= lastInterval;
        if (this.blinkTime < -0.2){
            this.blinkTime += 1;
        }
        
        var completeX = rect[0] + rect[2] / 2 - 75;
        var completeY = y + 20;

        var completeRect = [completeX, completeY, 150, 30];

        ctx.strokeStyle = (this.blinkTime > 0) ? '#000000' : '#ff0000';
        ctxRoundedRect(completeRect);

        ctx.fillStyle = this.textColor;
        ctx.drawImage(getImage(this.trainIcon), completeX + 5, completeY);
        ctx.fillText('Start Train', completeX + 45, completeY + 20);

        panelButtons.push([completeRect, this.completeFunction]);
    }

    entity.panelComponent.panelButtons = panelButtons;

    ctx.font = getFont(20);
    
    var closeRect = [rect[0] + rect[2] / 2 - 50, rect[1] + rect[3] - 50, 100, 40];
    this.drawPanelButton(closeRect, 'CLOSE');
    
    entity.panelComponent.panelButtons.push([closeRect, entityPanelClose]);    

    ctx.font = BASE_FONT;
};


var builtSourceMapSpawnPos = function(entity, world){
    var sourceEntities = _.filter(world.entities, function(chkEntity){
        return chkEntity.sourceExtensionComponent && 
            chkEntity.sourceExtensionComponent.sourceKey == entity.identityComponent.key;
    });
    
    sourceEntities.push(entity);

    var blockingEntities = world.getBlockingEntities();
    blockingEntities.push(entity);

    var blockingPositions = _.map(blockingEntities, 
                                  blockingEntity => [blockingEntity.positionComponent.x, blockingEntity.positionComponent.y]);
    var shallowBlockers = _.clone(blockingPositions);

    var checkForDifferentTrait = function(blockingEntity){
        if (!(blockingEntity.sourceExtensionComponent || blockingEntity.entityManagerComponent)){
            return false;
        }

        if (entity.personHeldComponent){
            if (!blockingEntity.personHeldComponent){
                return true;
            }
            return blockingEntity.personHeldComponent.personHeld != entity.personHeldComponent.personHeld;
        }

        return (blockingEntity.sourceExtensionComponent && 
                blockingEntity.sourceExtensionComponent.sourceKey != entity.identityComponent.key) ||
            (blockingEntity.entityManagerComponent && 
             blockingEntity.identityComponent.key != entity.identityComponent.key)
    };


    _.chain(blockingEntities)
        .filter(blockingEntity => checkForDifferentTrait(blockingEntity))
        .each(function(blockingEntity){
            _.each(world.hexMap.neighbors(blockingEntity.positionComponent.x, blockingEntity.positionComponent.y), 
                   function(pos){ blockingPositions.push(pos); });
        });

    var getAdjPos = (checkPositions) => {
        var positions = _.chain(sourceEntities)
            .map(posEntity => world.hexMap.neighbors(posEntity.positionComponent.x, posEntity.positionComponent.y))
            .flatten(true)
            .filter(function(neighbor){
                return world.isPosInBounds(neighbor) &&
                    !_.any(checkPositions, blockingPos => comparePos(blockingPos, neighbor));
            })
            .value();
        
        if (this.tendPosition){
            return _.min(positions, position => (Math.abs(position[0] - this.tendPosition[0]) + 
                                                 Math.abs(position[1] - this.tendPosition[1])));
        }
        else{
            return _.sample(positions);
        }
    };
    var adjPos = getAdjPos(blockingPositions);

    if (!adjPos){
        adjPos = getAdjPos(shallowBlockers);
    }

    if (!adjPos){
        this.spawnDone = true;
    }

    return adjPos;
};

var builtSourceMapShouldSpawn = function(entity, world, spawnedEntities){
    var level = entity.experienceComponent.getXpLevel(entity);
    var count = _.filter(world.entities, function(worldEntity){
        return worldEntity.sourceExtensionComponent &&
            worldEntity.sourceExtensionComponent.sourceKey == entity.identityComponent.key;
    }).length + 1 + spawnedEntities.length;
    return (count < level) && !this.spawnDone;
};


var killExtensions = function(entity, world){
    _.each(world.entitiesForKey(entity.mapSpawnComponent.mapSpawnKey), function(extension){
        extension.healthComponent.health = 0;
    });
};


var builtSourceCreateExec = function(entity, world){
    if (this.randomTraitPosition){
        randomOpenTraitPositionExec(entity, world, this.sector);
    }
    if (this.skipBuiltSourceCreate){
        return;
    }
    
    entity.experienceComponent.updateExpansion(entity);

    var spawnedEntities = [];

    while (entity.mapSpawnComponent.mapShouldSpawn(entity, world, [])){
        var spawnedEntity = entity.mapSpawnComponent.mapSpawn(entity, world, spawnedEntities);
        if (spawnedEntity){
            spawnedEntities.push(spawnedEntity);
            world.entityDeath();
        }
        else{
            break;
        }
    }

    spawnedEntities = [entity].concat(spawnedEntities);

    //spawnedEntities = _.shuffle(spawnedEntities);

    _.each(this.startManagedEntities, function(entityKey, i){
        world.makeEntities(entity, entityKey, {
            x: spawnedEntities[i].positionComponent.x,
            y: spawnedEntities[i].positionComponent.y,
            sourceTrait: entity.identityComponent.key
        });
    });
};

var randomOpenTraitPositionExec = function(entity, world, sector){
    var pos;
    if (entity.personHeldComponent){
        var blockingEntities = world.getBlockingEntities();
        var personEntityKeys = _.chain(world.entities)
            .filter(function(worldEntity){
                return worldEntity.personHeldComponent &&
                    worldEntity.personHeldComponent.personHeld == entity.personHeldComponent.personHeld &&
                    worldEntity.identityComponent &&
                    worldEntity.positionComponent;
            })
            .map(function(worldEntity){ return worldEntity.identityComponent.key; })
            .value();
        
        var positions = _.chain(world.entities)
            .filter(function(worldEntity){
                return (worldEntity.identityComponent &&
                        _.contains(personEntityKeys, worldEntity.identityComponent.key)) ||
                    (worldEntity.sourceExtensionComponent &&
                     _.contains(personEntityKeys, worldEntity.sourceExtensionComponent.sourceKey));
            }) 
            .map(function(worldEntity){
                return world.hexMap.neighbors(worldEntity.positionComponent.x, worldEntity.positionComponent.y);
            })
            .flatten(true)
            .filter(function(neighbor){
                return world.isPosInBounds(neighbor) &&
                    !_.any(blockingEntities, function(blockingEntity){
                        return blockingEntity.positionComponent.isPos(neighbor);
                    });
            })
            .value();
        
        if (positions.length > 0){
            if (sector){
                var sectorPosition = getSectorPos(sector);
                pos = _.min(positions, function(position){
                    var xdiff = Math.abs(position[0] - sectorPosition[0]);
                    var ydiff = Math.abs(position[1] - sectorPosition[1]);
                    return xdiff + ydiff;
                });
            }
            else{
                pos = _.sample(positions);
            }
            
            if (pos){
                entity.positionComponent.setPos(pos);
                return;
            }
        }
    }
    
    if (sector === undefined){
        pos = world.randomOpenPosition(entity.positionComponent.planeOfExistence);
        var entitiesToCheck = _.filter(world.entities, function(worldEntity){
            return ((worldEntity.identityComponent && worldEntity.identityComponent.key == PARAMETERS.self) ||
                    worldEntity.entityManagerComponent ||
                    worldEntity.sourceExtensionComponent);
        });
        
        var neighborPos = _.chain(world.hexMap.neighbors(pos[0], pos[1]))
            .filter(function(neighbor){
                if (!world.isPosInBounds(neighbor)){
                    return false;
                }
                var step = world.hexMap.neighbors(neighbor[0], neighbor[1]);
                return _.all(step, function(position){
                    return !_.any(entitiesToCheck, function(chkEntity){ return chkEntity.positionComponent.isPos(position); });
                });
            })
            .sample()
            .value();

        entity.positionComponent.setPos(neighborPos ? neighborPos : pos);
    }
    else{
        pos = false;
        var entitiesToCheck = world.getPositionedEntities(entity.positionComponent.planeOfExistence);
        var attempts = 0;
        while (!pos){
            pos = getSectorPos(sector);
            
            if (_.any(entitiesToCheck, function(chkEntity){
                return chkEntity.positionComponent.isPos(pos);
            })){
                pos = false;
                attempts += 1;
                if (attempts > 10){
                    break;
                }
            }
        }

        if (pos){
            entity.positionComponent.setPos(pos);
        }
        else{
            randomOpenTraitPositionExec(entity, world, undefined);
        }
    }
};

var getSectorPos = function(sector){
    return _.map(sector, function(sectorIndex, i){
        var sectorSize = PARAMETERS.size[i] / PARAMETERS.sectors[i];
        var specificIndex = sectorIndex + Math.random();
        var p = Math.floor(specificIndex * sectorSize);
        return min(max(p, 1), PARAMETERS.size[i] - 2);
    });
};


var builtSourceRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    var image = getImage(entity.entityManagerComponent.disabled ? this.disabledImage : this.image);
    var xDiff = this.getWobble(lastInterval);
    var x = rect[0] + xDiff;
    ctx.drawImage(image, x, rect[1]);

    var world = ui.world;
    var button = world.currentMakingEntity;
    if (button){
        if (button.buttonControlComponent.checkMakePos(world, entity.positionComponent.getAsPos())){
            ctx.drawImage(getImage(this.outlineImage), x, rect[1]);
        }
    }

    if (entity.entityManagerComponent.hasNew){
        this.timeToBlink -= lastInterval;
        if (this.timeToBlink > 0){
            ctx.drawImage(getImage(this.newImage), x, rect[1]);
        }
        else if (this.timeToBlink < -0.1){
            this.timeToBlink += 1;
        }
    }
};


var traitMenuCloseExec = function(entity, world){
    entity.entityManagerComponent.hasNew = false;
    _.chain(world.entities)
        .filter(worldEntity => (worldEntity.buttonControlComponent && 
                                worldEntity.buttonControlComponent.managerKey == entity.identityComponent.key))
        .each(worldEntity => worldEntity.buttonControlComponent.isNew = false);
};


var traitPanelRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    entity.panelComponent.panelButtons = [];
    if (entity.panelComponent.closed || entity.panelComponent.minimized){
        this.renderTime = 0;
        return;
    }

    this.snapPanelPos(entity);

    var world = ui.world;
    var trait = world.entityForUid(entity.panelComponent.sourceId);

    if (!trait){
        entity.panelComponent.closed = true;
        return;
    }

    var towerButtons = _.filter(world.entities, function(worldEntity){
        return worldEntity.buttonControlComponent &&
            worldEntity.buttonControlComponent.managerKey == trait.identityComponent.key;
    });

    var categories = _.chain(towerButtons)
            .map(function(button){ return button.uiDataComponent.subPanel; })
            .uniq()
            .sortBy(_.identity)
            .reverse()
            .value();

    if (!this.currentCategory){
        this.currentCategory = categories[0];
    }

    var getButtons = () => _.filter(towerButtons, towerButton => towerButton.uiDataComponent.subPanel == this.currentCategory);
    
    var buttons = getButtons();

    if (buttons.length == 0 && towerButtons.length > 0){
        this.currentCategory = towerButtons[0].uiDataComponent.subPanel;
        buttons = getButtons();
    }

    entity.absolutePositionComponent.size[1] = 275 + (buttons.length + 1) * 70;
    rect = entity.absolutePositionComponent.getAbsPosRect();

    if (this.renderTime < this.animatingTime){
        this.renderTime += lastInterval;
        var fraction = this.renderTime / this.animatingTime;
        ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2] * fraction, rect[3] * fraction);
        return;
    }
    
    ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);

    entity.panelComponent.panelButtons.push(this.drawHelpButton(entity, rect));

    ctx.fillStyle = this.textColor;
    ctx.font = getFont(40);
    
    var titleWidth = ctx.measureText(trait.identityComponent.name).width;

    ctx.fillText(trait.identityComponent.name, rect[0] + (rect[2] - titleWidth) / 2, rect[1] + 60);

    ctx.font = getFont(20);

    if (trait.identityComponent.sourceName && trait.memoryComponent){
        ctx.fillText('Created by: ' + trait.identityComponent.sourceName, rect[0] + 10, rect[1] + 100);
    }

    if (trait.descriptionComponent.description){
        var y = rect[1] + 140;
        ctx.font = (trait.descriptionComponent.description.length > 75) ? '14px OpenSansItalic' : '20px OpenSansItalic';

        ctx.fillText(trait.descriptionComponent.description, rect[0] + 10, y);
        
        ctx.font = getFont(20);
    }
    
    y = rect[1] + 200;

    var standardRender = true;
    if (trait.entityManagerComponent.disabled){
        var disablingEntity = _.chain(world.entities)
            .filter(worldEntity => worldEntity.disableTraitComponent && worldEntity.disableTraitComponent.checkTraitDisabled(trait))
            .first()
            .value();
        ctx.fillText('Disabled: ' + disablingEntity.identityComponent.name, rect[0] + 10, y);
        standardRender = false;
    }
    else if (!trait.interactionComponent.canInteractSelf()){
        ctx.fillText('You have no control over this trait.', rect[0] + 10, y);
        standardRender = false;
    }
    else if (!_.contains(trait.interactionComponent.connectionKeys, PARAMETERS.self)){
        var start = world.entityForKey(PARAMETERS.self).positionComponent.getAsPos();
        var end = trait.positionComponent.getAsPos();
        if (!determinePath(start, end, world.getAllPaths())){
            ctx.fillText('You need to connect to this trait before you can build on it.', rect[0] + 10, y);
            standardRender = false;
        }
        else{
            trait.interactionComponent.connectionKeys.push(PARAMETERS.self);
        }
    }

    if (standardRender){
        _.each(categories, (panelKey, i) => {
            var catWidth  = 140;
            var x = rect[0] + 10 + (catWidth + 10) * i;
            if (panelKey == this.currentCategory){
                ctx.strokeStyle = this.insetColor;
                
                var textWidth = ctx.measureText(panelKey).width;
                ctx.fillText(panelKey, x + catWidth / 2 - textWidth / 2, y - 10);
                ctx.fillRect(x, y - 5, catWidth, 2);
            }
            else{
                var panelRect = [x, y - 30, catWidth, 30];

                var color = _.any(towerButtons, button => button.uiDataComponent.subPanel == panelKey && button.buttonControlComponent.isNew) ? '#ffd700' : '#000000';

                this.insetColor = color;
                
                this.drawPanelButton(panelRect, panelKey);

                this.insetColor = '#000000';
                
                if (!this.setCategoryFn[panelKey]){
                    this.setCategoryFn[panelKey] = () => {
                        this.currentCategory = panelKey;
                    };
                }
                var setCategoryFn = this.setCategoryFn[panelKey];
                entity.panelComponent.panelButtons.push([panelRect, setCategoryFn]);
            }
        });

        
        
        if (buttons.length > 0){
            y += 70;
            _.each(buttons, (button, i) => {
                ctx.fillStyle = this.textColor;
                
                ctx.drawImage(getImage(trait.uiDataComponent.image), rect[0] + 15, y - 26);
                button.uiDataComponent.drawImage(button, world, [rect[0] + 15, y - 26]);
                                
                ctx.fillText(button.identityComponent.name, rect[0] + 80, y + 10);

                ctx.font = '14px OpenSansItalic';
                if (_.isArray(button.descriptionComponent.description)){
                    _.each(button.descriptionComponent.description, (desc, i) => {
                        ctx.fillText(desc, rect[0] + 320, y + 15 * i);
                    });
                }
                else{
                    if (button.descriptionComponent.description.length > 50){
                        ctx.font = '12px OpenSansItalic';
                    }
                    ctx.fillText(button.descriptionComponent.description, rect[0] + 325, y + 10);
                }
                
                if (!button.interactionComponent.canInteractSelf()){
                    ctx.font = getFont(14);
                    ctx.fillText('No Control', rect[0] + rect[2] - 100, y + 7);
                    ctx.font = getFont(20);
                }
                else if (button.buttonControlComponent.canMakeEntity(world)){
                    ctx.font = getFont(20);
                    var buttonRect = [rect[0] + rect[2] - 100, y - 15, 70, 30];

                    this.drawPanelButton(buttonRect, 'Build')
                    
                    var buildFunction = button.buttonControlComponent.getWrappedFunction(button, world, ui);
                    entity.panelComponent.panelButtons.push([buttonRect, buildFunction]);
                }
                else{
                    ctx.font = getFont(14);
                    ctx.fillText('Limit', rect[0] + rect[2] - 100, y);
                    ctx.fillText('Reached', rect[0] + rect[2] - 100, y + 15);
                    ctx.font = getFont(20);
                }

                if (button.buttonControlComponent.displayRiskIcon){
                    ctx.drawImage(getImage('dice'), rect[0] + 11, y - 29, 18, 18);
                }

                ctx.strokeStyle = button.buttonControlComponent.isNew ? '#ffd700' : '#000000';
                ctxRoundedRect([rect[0] + 10, y - 30, rect[2] - 20, 60]);
                ctx.strokeStyle = '#000000';
                y += 70;
            });
        }
    }
    
    var closeRect = [rect[0] + rect[2] / 2 - 50, rect[1] + rect[3] - 50, 100, 40];
    this.drawPanelButton(closeRect, 'CLOSE');
    entity.panelComponent.panelButtons.push([closeRect, entityPanelClose]);    

    ctx.font = BASE_FONT;
};


var checkTraitParticleEffectAlive = function(entity, world){
    var manager = world.entityForKey(this.managerKey);
    return manager && manager.entityManagerComponent.hasNew;
};


var transformTrait = function(entity, world){
    world.transformTrait(this.sourceTrait, this.newTrait, this.retainHealth);
};


var tutorialTextRender = function(rect, entity, ui){
    var world = ui.world;
    var entityToMark = world.entityForKey(this.entityToMark);
    
    if (entityToMark.positionComponent){
        rect = _.clone(world.hexMap.cellForPosComp(entityToMark.positionComponent).rect);
    }
    else if (entityToMark.absolutePositionComponent){
        rect = entityToMark.absolutePositionComponent.getAbsPosRect();
    }
    else{
        rect = [0, 0, 0, 0];
    }
    
    ctx.fillStyle = '#ffffff';
    ctx.font = getFont(30);

    var y = max(rect[1] + this.yOffset - 20, 32);

    ctx.fillText(this.text, rect[0] + this.xOffset, y);
    var textWidth = ctx.measureText(this.text).width;
    ctx.font = BASE_FONT;
    
    this.drawArrow([rect[0] + this.xOffset + textWidth / 2, y + 10],
                   [rect[0] + rect[2], rect[1] + rect[3] / 2]);
};

var tutorialTextDrawArrow = function(startPos, endPos){
    ctx.strokeStyle = '#ffffff';
    ctx.beginPath();
    ctx.moveTo(startPos[0], startPos[1]);
    ctx.bezierCurveTo(startPos[0] + 20, startPos[1] + 20,
                      endPos[0] + 10, endPos[1],
                      endPos[0], endPos[1]);
    ctx.lineTo(endPos[0] + 10, endPos[1] - 6);
    ctx.moveTo(endPos[0], endPos[1]);
    ctx.lineTo(endPos[0] + 10, endPos[1] + 6);
    ctx.stroke();
};


var tutorialTextTutorialDone = function(entity, world){
    var markingEntity = world.entityForKey(entity.tutorialTextComponent.entityToMark);
    if (!markingEntity){
        return true;
    }

    return this.checkTutorialUiKey(markingEntity, world);
    
};

var checkTutorialUiKey = function(markingEntity, world){
    var uiKey = markingEntity.clickComponent.uiKey;
    var panel = world.entityForKey(uiKey);
    if (panel === undefined){
        return false;
    }
    return !panel.panelComponent.closed;
};


var tutorialUnpauseRender = function(rect, entity, ui){
    ctx.fillStyle = '#ffffff';
    ctx.font = getFont(30);
    _.each(this.text, (txt, i) => ctx.fillText(txt, 50, this.yStart + 50 * i));
    ctx.font = BASE_FONT;

    ctx.strokeStyle = '#ffffff';
    ctx.beginPath();
    ctx.moveTo(50, this.yStart + 50 * (this.text.length - 1) + 25);
    ctx.bezierCurveTo(25, 625,
                      25, 700,
                      50, 760);
    ctx.lineTo(44, 754);
    ctx.moveTo(50, 760);
    ctx.lineTo(56, 754);
    ctx.stroke();
};


var tutorialUnpauseTutorialDone = function(entity, world){
    return world.game.tickSystemWrapper.running;
};


var getUiActions = function(entity, ui, panel){
    if (!(this.memoizedActions && (Object.keys(this.memoizedActions).length > 0) && _.all(this.memoizedActions, _.identity))){
        this.memoizedActions = this.generateUiActions(entity, ui, panel);
    }
    
    return this.memoizedActions;
};


var uiDataDrawImage = function(entity, world, pos){
    var managerKey = false;
    if (entity.managedComponent){
        managerKey = entity.managedComponent.managerKey;
    }
    if (entity.buttonControlComponent){
        managerKey = entity.buttonControlComponent.managerKey;
    }
    
    if (managerKey){
        var imageKey;
        if (allArgs[managerKey]){
            imageKey = allArgs[managerKey].image;
        }
        else{
            var manager = world.entityForKey(managerKey);
            if (manager){
                imageKey = manager.uiDataComponent.image;
            }
        }

        if (imageKey){
            ctx.drawImage(getImage(imageKey), pos[0], pos[1]);
        }
    }
    ctx.drawImage(getImage(this.image), pos[0], pos[1]);
};


var upgradeTower = function(world, pos, entity, interactionKey){
    var toUpgrade = this.baseUpgradeTowerForPos(world, pos);
        
    toUpgrade.healthComponent.kill();

    var args = {
        x: pos[0],
        y: pos[1],
        sourceKey: toUpgrade.managedComponent.managerKey,
        sourceTrait: toUpgrade.managedComponent.managerKey
    };

    if (interactionKey){
        args.interactionKeys = [interactionKey];
    }
    
    var newEntity = makeEcs(this.constructionKey, args);
    
    return newEntity;
};

var canMakeUpgrade = function(world){
    return _.any(this.baseKeys, function(baseKey){
        return world.entityForKeyExists(baseKey);
    });
};

var checkUpgradePos = function(world, pos){
    return this.baseUpgradeTowerForPos(world, pos) !== undefined;
};

var baseUpgradeTowerForPos = function(world, pos){
    return _.chain(this.baseKeys)
        .map(function(baseKey){
            return world.entitiesForKey(baseKey);
        })
        .flatten(true)
        .filter(function(baseEntity){
            return baseEntity.positionComponent.isPos(pos);
        })
        .first()
        .value();
};



var utilityEntityRender = function(rect, entity, ui){
    ctx.drawImage(getImage(this.backImage), rect[0], rect[1], rect[2], rect[3]);
    entity.panelComponent.panelButtons = [];

    if (!this.showAllBuildingPanel){
        this.showAllBuildingPanel = function(){
            var menu = ui.world.entityForKey(PARAMETERS.allBuildingPanel);
            if (!menu){
                menu = makeEcs(PARAMETERS.allBuildingPanel);
                ui.world.addEntity(menu);
            }
            
            menu.panelComponent.expandPanel();
        };
    }

    ctx.fillStyle = this.textColor;
    
    var buildingRect = [rect[0] + rect[2] / 2 - 75, rect[1] + rect[3] / 2 - 15, 150, 30];
    this.drawPanelButton(buildingRect, 'All Buildings');
    entity.panelComponent.panelButtons.push([buildingRect, this.showAllBuildingPanel]);
};


var getWobble = function(lastInterval){
    var xDiff = 0;
    if (this.wobble > 0){
        xDiff = 3 * Math.sin(this.wobble);
        this.wobble -= lastInterval * 8 * Math.PI;
    }
    return xDiff;
}


var calculateWorkerTicks = function(entity, world){
    var xpFactor = 1;
    if (entity.managedComponent){
        var manager = world.entityForKey(entity.managedComponent.managerKey);
        xpFactor = (1 - Math.pow(1/2, manager.experienceComponent.currentLevel)) * 2;
    }
    var workerFactor = (1 - Math.pow(1/2, this.workersActive.length)) * 2;
    return xpFactor * workerFactor;
};


var checkWorldGoalComponent = function(entity){
    return _.all(entity.worldGoalComponent.currencyRequested, function(val){ return val <= 0; });
};


var makeGoalMenuExecute = function(entity, world){
    if (entity.interactionComponent.canInteractSelf()){
        world.makeEntities(entity, this.goalMenuKey);
    }
};


var trackActionables = function(entity, world){
    world.trackActionables();
};


var worldGoalRender = function(rect, entity, ui, rectCount, timeToNextTick, lastInterval){
    var x = rect[0] + this.getWobble(lastInterval);
    ctx.drawImage(getImage(this.image), x, rect[1]);
    ctx.fillStyle = entity.worldGoalComponent.requestingCurrency ? '#ffffff' : '#000000';
    
    if (this.outlineImage){
        ctx.drawImage(getImage(this.outlineImage), x, rect[1]);
        this.outlineImage = false;

        this.renderTrainIcon(lastInterval, x, rect[1]);
    }

    if (!entity.worldGoalComponent.goalSeen && entity.interactionComponent.canInteractSelf()){
        this.timeToBlink -= lastInterval;
        if (this.timeToBlink > 0){
            ctx.drawImage(getImage(this.newImage), x, rect[1]);
        }
        else if (this.timeToBlink < -0.1){
            this.timeToBlink += 1;
        }
    }
};


var worldGoalGetCurrencyComponent = function(entity){
    return entity.worldGoalComponent;
};


var worldGoalMenuOpened = function(entity, args){
    if (args.menuSourceKey == entity.uid){
        entity.worldGoalComponent.goalSeen = true;
        entity.pauseComponent.paused = false;
    }
};


var chkForPlayerInterruptExec = function(entity, world){
    if (entity.interactionComponent.canInteract(PARAMETERS.self)){
        this.playerExecute.execute(entity, world);
    }
    else{
        entity.pauseComponent.paused = false;
    }
};


var checkWorldGoalParticleEffectAlive = function(entity, world){
    var source = world.entityForUid(this.sourceId);
    if (!source){
        return false;
    }
    return !source.worldGoalComponent.goalSeen;
};


var angerTickModifyInteraction = function(entity, world, tower, train){
    if (percentCheck(5)){
        train.healthComponent.health = 0;
        var entitiesToMake = 'anger';
        world.makeEntities(tower, entitiesToMake);
    }
    return true;
};


var buyRingCreateExec = function(entity, world){
    _.each(this.keysToKill, function(keyToKill){
        var entitiesToKill = world.entitiesForKey(keyToKill);
        _.each(entitiesToKill, function(entityToKill){
            if (!entityToKill.personHeldComponent ||
                (entityToKill.personHeldComponent.personHeld != entity.personHeldComponent.personHeld)){
                entityToKill.healthComponent.health = 0;
            }
        })
    });
};


var datingCreate = function(entity, world){
    _.each(world.entitiesForKey('askOutGoal'), function(worldEntity){
        worldEntity.healthComponent.health = 0;
    });

    _.each(world.entitiesForKey('fantasyMine'), function(worldEntity){
        if (worldEntity.personHeldComponent.personHeld == entity.personHeldComponent.personHeld){
            worldEntity.healthComponent.health = 0;

            var newMine = makeEcs('daydreamMine', {
                x: worldEntity.positionComponent.x,
                y: worldEntity.positionComponent.y,
                sourceKey: worldEntity.managedComponent.managerKey,
                sourceTrait: worldEntity.managedComponent.managerKey
            });
            world.addEntity(newMine);
        }
    });
};


var makeBuyRingGoalCreate = function(entity, world){
    var datingOpinion = _.sample(world.entitiesForKey('dating'));
    entity.personHeldComponent.personHeld = datingOpinion.personHeldComponent.personHeld;
};


var addRandomPersonToMap = function(entity, world){
    var peopleOnMap = _.chain(world.entities)
        .filter(function(worldEntity){
            return worldEntity.personalityComponent;
        })
        .map(function(worldEntity){
            return worldEntity.identityComponent.key;
        })
        .value();

    var peopleLeft = _.filter(csvIdentifiers.person, function(key){ return !_.contains(peopleOnMap, key); });

    var person = _.sample(peopleLeft);
            
    if (person){
        world.makeEntities(entity, person);
    }
    
    var button = world.entityForKey('meetPersonButton');
    button.healthComponent.kill();
};


var tutorialDefenseTabTutorialDone = function(entity, world){
    var markingEntity = _.filter(world.entities, entity => entity.key == 'traitPanel' && entity.identityComponent.key.startsWith('adult'))[0];
    if (!markingEntity){
        return true;
    }

    return markingEntity.rendererComponents[0].currentCategory == 'Defenses';
};


var checkTutorialFinishTrain = function(markingEntity, world){
    return _.any(world.entities, entity => entity.trainPathComponent);
};


var checkTutorialIncludeWill = function(markingEntity, world){
    return _.any(world.entities, entity => entity.trainPathMakerComponent && entity.trainPathMakerComponent.stops.length > 1);
};


var checkTutorialPlaceEnergyKey = function(markingEntity, world){
    return _.any(world.entities, entity => entity.constructionComponent);
};


var checkTutorialAdulthoodUiKey = function(markingEntity, world){
    var source = world.entityForKey(markingEntity.sourceExtensionComponent.sourceKey);
    var panel = world.entityForKey(source.clickComponent.uiKey);
    if (panel === undefined){
        return false;
    }
    return !panel.panelComponent.closed;
};


var tutorialTextAdultPanelDone = function(entity, world){
    return world.currentMakingEntity;
};


var tutorialWaitForEnergyDone = function(entity, world){
    return world.entityForKey(this.keyToCheck);
};


var tutorialWrongButtonRender = function(rect, entity, ui){
    var panel = ui.world.entityForKey(PARAMETERS.trainPathMakingPanel);

    if (panel && !panel.panelComponent.closed){
        rect = panel.absolutePositionComponent.getAbsPosRect();

        ctx.fillStyle = '#ffffff';
        ctx.font = getFont(30);
        ctx.fillText(this.text, rect[0] - 200, rect[1] + rect[3] - 75);
        ctx.font = BASE_FONT;
        
        ctx.strokeStyle = '#ffffff';
        ctx.beginPath();
        ctx.moveTo(rect[0] - 175, rect[1] + rect[3] - 65);
        ctx.bezierCurveTo(rect[0] - 150, rect[1] + rect[3] - 30,
                          rect[0] - 40, rect[1] + rect[3] - 30,
                          rect[0] - 10, rect[1] + rect[3] - 25);
        ctx.lineTo(rect[0] - 16, rect[1] + rect[3] - 31);
        ctx.moveTo(rect[0] - 10, rect[1] + rect[3] - 25);
        ctx.lineTo(rect[0] - 16, rect[1] + rect[3] - 19);
        ctx.stroke();
    }
    else{
        entity.healthComponent.kill();
    }
};


var workCompetitionTick = function(entity, world){
    this.ticksLeft -= 1;

    if (this.ticksLeft % 30 == 10){
        entity.workCompetitionComponent.oppWorkCompleted += 1;
    }

    if (this.ticksLeft <= 0){
        entity.healthComponent.health = 0;
        _.each(entitiesToKill, function(key){
            var toKill = world.entityForKey(key);
            toKill.healthComponent.health = 0;
        });
    }
};


var workCompetitionRender = function(rect, entity, ui){
    ctx.fillStyle = '#ffffff';
    ctx.fillText(entity.identityComponent.name, rect[0] + rect[2] / 3, rect[1] + rect[3] / 4);

    ctx.fillText('' + entity.workCompetitionComponent.workCompleted, rect[0] + rect[2] / 3, rect[1] + rect[3] / 2);
    ctx.fillText('' + entity.workCompetitionComponent.oppWorkCompleted, rect[0] + rect[2] / 3, rect[1] + 3 * rect[3] / 4);
};


var workCompetitionNotify = function(entity, args){
    if (args.key == this.goalKey){
        entity.workCompetitionComponent.workCompleted += 1;
    }
};



var csvIdentifiers = {
    "sourceExtension": [
        "adult_ext", 
        "city_ext", 
        "intelligent_ext", 
        "motherland_ext", 
        "personB_ext", 
        "personD_ext"
    ], 
    "workCompetition": [
        "cleanCompete", 
        "cabCompete"
    ], 
    "qualityResource": [], 
    "resourceModifier": [
        "angerResourceModifier", 
        "joyResourceModifier", 
        "sadnessResourceModifier", 
        "fearResourceModifier"
    ], 
    "personGoal": [
        "askOutGoal", 
        "restaurantPrepGoal", 
        "restaurantDateGoal", 
        "parkPrepGoal", 
        "parkDateGoal", 
        "walkDateGoal", 
        "socialGoal", 
        "supportGoal", 
        "askAboutGoal", 
        "learnAboutGoal", 
        "calmDownGoal", 
        "buyTimeGoal", 
        "buyRingGoal", 
        "proposeGoal", 
        "selfControlGoal", 
        "spendTimeGoal", 
        "justTalkGoal"
    ], 
    "changeOpinion": [], 
    "worldGoal": [
        "wakeGoal", 
        "unpackGoal", 
        "readHomeGoal", 
        "exploreGoal", 
        "findCleanJobGoal", 
        "findCabJobGoal", 
        "jobQuotaGoal", 
        "cleanChoreGoal", 
        "cabChoreGoal", 
        "activationGoal", 
        "learnFirstMissionGoal", 
        "papersGoal", 
        "cleanHouseGoal", 
        "fightProgramGoal", 
        "spyGoal", 
        "learnSpyNewsGoal", 
        "confessionGoal", 
        "rentGoal", 
        "workChoreGoal", 
        "meetPersonGoal", 
        "cleanWorkHouseGoal", 
        "planEscapeGoal", 
        "buyEscapeGoal", 
        "escapeGoal", 
        "disavowalGoal", 
        "bombPrep1Goal", 
        "bombPrep2Goal", 
        "bombPrep3Goal", 
        "missionGoal", 
        "workShiftGoal", 
        "memoryStopGoal", 
        "openMindGoal"
    ], 
    "goalCompleteMenu": [
        "readHomeMenu", 
        "findCleanJobMenu", 
        "findCabJobMenu", 
        "jobQuotaMenu", 
        "activationMenu", 
        "fightProgramMenu", 
        "learnSpyNewsMenu", 
        "learnAboutMenu", 
        "calmDownMenu", 
        "restaurantDateMenu", 
        "parkDateMenu", 
        "walkDateMenu", 
        "escapeMenu", 
        "missionMenu", 
        "killedMenu", 
        "proposeMenu", 
        "breakdownMenu", 
        "workShiftMenu", 
        "askOutMenu", 
        "meetPersonMenu", 
        "papersMenu", 
        "spyMenu", 
        "loseJobMenu", 
        "wakeMenu", 
        "unpackMenu", 
        "exploreMenu", 
        "cleanChoreMenu", 
        "cabChoreMenu", 
        "learnFirstMissionMenu", 
        "cleanHouseMenu", 
        "confessionMenu", 
        "rentMenu", 
        "workChoreMenu", 
        "cleanWorkHouseMenu", 
        "planEscapeMenu", 
        "buyEscapeMenu", 
        "disavowalMenu", 
        "bombPrep1Menu", 
        "bombPrep2Menu", 
        "bombPrep3Menu", 
        "memoryStopMenu", 
        "openMindMenu", 
        "restaurantPrepMenu", 
        "parkPrepMenu", 
        "socialMenu", 
        "supportMenu", 
        "askAboutMenu", 
        "buyTimeMenu", 
        "buyRingMenu", 
        "selfControlMenu", 
        "spendTimeMenu", 
        "justTalkMenu", 
        "understandMemoryMenu", 
        "flareMemoryMenu"
    ], 
    "currency": [
        "cash", 
        "caution", 
        "cheer", 
        "chore", 
        "confession", 
        "contact", 
        "desire", 
        "disavowal", 
        "pain", 
        "energy", 
        "experience", 
        "frustration", 
        "impulse", 
        "industry", 
        "letter", 
        "preparation", 
        "sin", 
        "spy", 
        "sympathy", 
        "thought", 
        "travel", 
        "understand", 
        "will", 
        "work"
    ], 
    "fadingMemory": [], 
    "instantEmotionResource": [], 
    "randomReplace": [
        "dateReplace", 
        "dateComplete"
    ], 
    "emotionResourceMap": [
        "angerMap", 
        "joyMap", 
        "sadnessMap", 
        "fearMap"
    ], 
    "memoryGoalButton": [
        "understandMemoryButton", 
        "flareMemoryButton"
    ], 
    "disableTrait": [
        "disablePersonD"
    ], 
    "gameStage": [
        "chapter1", 
        "chapter2", 
        "chapter3", 
        "chapter4", 
        "chapter5", 
        "chapter6"
    ], 
    "creep": [
        "anger", 
        "joy", 
        "sadness", 
        "fear"
    ], 
    "personCurrencyAction": [
        "confessAction_personB", 
        "confessAction_personD", 
        "contactAction_personB", 
        "contactAction_personD", 
        "dateAction_personB", 
        "dateAction_personD", 
        "flirtAction_personB", 
        "flirtAction_personD", 
        "talkAction_personB", 
        "talkAction_personD"
    ], 
    "selfExtension": [], 
    "menuTutorialText": [
        "tutorialTextWakeUpPanel", 
        "tutorialTextWakeUpPanel2", 
        "tutorialTextAdultPanel", 
        "tutorialTextEnergyPanel", 
        "tutorialTextEnergyPanel2", 
        "tutorialTextWakeTrain", 
        "tutorialPreDefense", 
        "tutorialDefenseTab", 
        "tutorialBuildPaint", 
        "tutorialBuildPaint2"
    ], 
    "person": [
        "personB", 
        "personD"
    ], 
    "helpInfoPanel": [
        "towerHelpInfoPanel", 
        "goalHelpInfoPanel", 
        "traitHelpInfoPanel", 
        "trainPathHelpInfoPanel", 
        "dailyHelpInfoPanel"
    ], 
    "transformExec": [], 
    "towerTickModifier": [
        "angerTickModifier", 
        "joyTickModifier", 
        "sadnessTickModifier", 
        "fearTickModifier"
    ], 
    "source": [
        "self"
    ], 
    "memoryTower": [
        "applyTower", 
        "empathizeTower", 
        "rechargeTower", 
        "prepareTower"
    ], 
    "memory": [
        "memory_nastyComment", 
        "memory_defineSelf", 
        "memory_betrayal", 
        "memory_threat", 
        "memory_acceptFear", 
        "memory_fearFlash", 
        "memory_programming", 
        "memory_confessFear", 
        "memory_lifeThreat", 
        "memory_dateGood", 
        "memory_prideJoy", 
        "memory_askOutSuccess", 
        "memory_ringExcite", 
        "memory_dateGreat", 
        "memory_dateLucky", 
        "memory_missHome", 
        "memory_dateSad", 
        "memory_consequenceSad", 
        "memory_learnSad", 
        "memory_shame", 
        "memory_dateFail", 
        "memory_disavowAnger", 
        "memory_disavowSadness"
    ], 
    "entityReplace": [
        "nullReplace", 
        "findJob", 
        "agent", 
        "learnFirstMission"
    ], 
    "endGoal": [], 
    "killTrait": [
        "killMotherland"
    ], 
    "personGoalButton": [
        "askOutButton", 
        "socialButton", 
        "supportButton", 
        "askAboutButton", 
        "buyTimeButton", 
        "buyRingButton"
    ], 
    "flagbranch": [], 
    "upgradeButton": [], 
    "menuModifier": [], 
    "emotionResource": [
        "nastyCommentMine", 
        "defineSelfMine", 
        "betrayalMine", 
        "threatMine", 
        "acceptFearMine", 
        "fearFlashMine", 
        "programmingMine", 
        "confessFearMine", 
        "lifeThreatMine", 
        "dateGoodMine", 
        "prideJoyMine", 
        "askOutSuccessMine", 
        "ringExciteMine", 
        "dateGreatMine", 
        "dateLuckyMine", 
        "missHomeMine", 
        "dateSadMine", 
        "consequenceSadMine", 
        "learnSadMine", 
        "shameMine", 
        "dateFailMine", 
        "disavowAngerMine", 
        "disavowSadnessMine", 
        "flashback_anger", 
        "flashback_joy", 
        "flashback_fear", 
        "flashback_sadness"
    ], 
    "resourceButton": [
        "willMineButton", 
        "energyMineButton", 
        "industryMineButton", 
        "homeThoughtMineButton", 
        "thoughtMineButton", 
        "desireMineButton"
    ], 
    "notifyEntityMaker": [
        "cleanPrideCheck", 
        "cabPrideCheck", 
        "cleanChoreStartCheck", 
        "cabChoreStartCheck", 
        "cleanCompeteCheck", 
        "cabCompeteCheck"
    ], 
    "steamAchievement": [
        "steamEscape", 
        "steamJob", 
        "steamMarriage", 
        "steamSpy"
    ], 
    "dailyEntityMaker": [
        "rentMaker", 
        "job", 
        "cleanChore", 
        "cabChore"
    ], 
    "currencyAction": [
        "travelAction", 
        "choreAction", 
        "cleanAction", 
        "cabAction", 
        "spyAction", 
        "paintAction", 
        "understandAction", 
        "chessAction", 
        "planAction", 
        "thinkAboutAction", 
        "driveAction", 
        "walkAction", 
        "disavowalAction", 
        "contactAction", 
        "applyAction", 
        "empathizeAction", 
        "rechargeAction", 
        "prepareAction"
    ], 
    "personTowerButton": [
        "confessButton_personB", 
        "contactButton_personB", 
        "dateButton_personB", 
        "flirtButton_personB", 
        "talkButton_personB", 
        "confessButton_personD", 
        "contactButton_personD", 
        "dateButton_personD", 
        "flirtButton_personD", 
        "talkButton_personD"
    ], 
    "entityKill": [
        "killWork", 
        "killFindJob"
    ], 
    "emotionSource": [
        "nastyComment", 
        "defineSelf", 
        "betrayal", 
        "threat", 
        "acceptFear", 
        "fearFlash", 
        "programming", 
        "confessFear", 
        "lifeThreat", 
        "dateGood", 
        "prideJoy", 
        "askOutSuccess", 
        "ringExcite", 
        "dateGreat", 
        "dateLucky", 
        "missHome", 
        "dateSad", 
        "consequenceSad", 
        "learnSad", 
        "shame", 
        "dateFail", 
        "disavowAnger", 
        "disavowSadness"
    ], 
    "gainControlMenu": [
        "gainControl_anger", 
        "gainControl_joy", 
        "gainControl_sadness", 
        "gainControl_fear"
    ], 
    "personResource": [
        "socialMine_personB", 
        "socialMine_personD", 
        "fantasyMine_personB", 
        "fantasyMine_personD", 
        "daydreamMine_personB", 
        "daydreamMine_personD", 
        "impressMine_personB", 
        "impressMine_personD"
    ], 
    "tutorialUnpause": [
        "tutorialUnpauseBuild", 
        "tutorialFinish"
    ], 
    "gameStageStartMenu": [
        "chapter2Menu", 
        "chapter3Menu", 
        "chapter4Menu", 
        "chapter5Menu", 
        "chapter6Menu"
    ], 
    "memoryConstruction": [
        "frustrationMineConstruction", 
        "painMineConstruction", 
        "cheerMineConstruction", 
        "cautionMineConstruction", 
        "applyTowerConstruction", 
        "empathizeTowerConstruction", 
        "rechargeTowerConstruction", 
        "prepareTowerConstruction", 
        "understandMemoryGoalConstruction", 
        "flareMemoryGoalConstruction"
    ], 
    "trait": [
        "adult", 
        "city", 
        "intelligent", 
        "motherland"
    ], 
    "tutorialText": [
        "tutorialTextWakeUp", 
        "tutorialTextAdulthood", 
        "tutorialPlaceEnergy", 
        "tutorialIncludeWill", 
        "tutorialFinishTrain", 
        "tutorialTextWakeUpFinish", 
        "tutorialTextEnergy", 
        "tutorialFinishGoalTrain", 
        "tutorialDefense"
    ], 
    "addPersonAndOpinion": [
        "makeAgentE", 
        "makeTarget"
    ], 
    "goalButton": [
        "workShiftButton", 
        "findCleanJobButton", 
        "findCabJobButton", 
        "fightProgramButton", 
        "meetPersonButton"
    ], 
    "resource": [
        "willMine", 
        "energyMine", 
        "industryMine", 
        "homeThoughtMine", 
        "thoughtMine", 
        "desireMine"
    ], 
    "traitMaker": [
        "sadTraitMaker", 
        "happyTraitMaker"
    ], 
    "towerButton": [
        "choreButton", 
        "travelButton", 
        "cleanButton", 
        "cabButton", 
        "spyButton", 
        "paintButton", 
        "walkButton", 
        "understandButton", 
        "chessButton", 
        "planButton", 
        "thinkAboutButton", 
        "driveButton", 
        "disavowalButton", 
        "contactButton"
    ], 
    "traitParticleEffect": [
        "newTraitParticle"
    ], 
    "memoryResource": [
        "frustrationMine", 
        "painMine", 
        "cautionMine", 
        "cheerMine"
    ], 
    "memoryGoal": [
        "understandMemoryGoal", 
        "flareMemoryGoal"
    ], 
    "worldGoalParticleEffect": [
        "newParticle"
    ], 
    "project": [
        "chessProject"
    ], 
    "memoryResourceButton": [
        "frustrationMineButton", 
        "painMineButton", 
        "cheerMineButton", 
        "cautionMineButton"
    ], 
    "construction": [
        "confessTower_personBConstruction", 
        "contactTower_personBConstruction", 
        "dateTower_personBConstruction", 
        "flirtTower_personBConstruction", 
        "talkTower_personBConstruction", 
        "confessTower_personDConstruction", 
        "contactTower_personDConstruction", 
        "dateTower_personDConstruction", 
        "flirtTower_personDConstruction", 
        "talkTower_personDConstruction", 
        "willMineConstruction", 
        "energyMineConstruction", 
        "industryMineConstruction", 
        "homeThoughtMineConstruction", 
        "thoughtMineConstruction", 
        "desireMineConstruction", 
        "choreTowerConstruction", 
        "travelTowerConstruction", 
        "cleanTowerConstruction", 
        "cabTowerConstruction", 
        "spyTowerConstruction", 
        "paintTowerConstruction", 
        "walkTowerConstruction", 
        "understandTowerConstruction", 
        "chessTowerConstruction", 
        "planTowerConstruction", 
        "thinkAboutTowerConstruction", 
        "driveTowerConstruction", 
        "disavowalTowerConstruction", 
        "contactTowerConstruction", 
        "socialMine_personBConstruction", 
        "fantasyMine_personBConstruction", 
        "daydreamMine_personBConstruction", 
        "impressMine_personBConstruction", 
        "socialMine_personDConstruction", 
        "fantasyMine_personDConstruction", 
        "daydreamMine_personDConstruction", 
        "impressMine_personDConstruction", 
        "workShiftGoalConstruction", 
        "findCleanJobGoalConstruction", 
        "findCabJobGoalConstruction", 
        "fightProgramGoalConstruction", 
        "meetPersonGoalConstruction", 
        "askOutGoalConstruction", 
        "socialGoalConstruction", 
        "supportGoalConstruction", 
        "askAboutGoalConstruction", 
        "buyTimeGoalConstruction", 
        "buyRingGoalConstruction"
    ], 
    "opinion": [
        "acquaintance", 
        "dating", 
        "friend", 
        "knownAgent", 
        "married", 
        "crush", 
        "target", 
        "worried", 
        "insecure", 
        "hasProject", 
        "needsSupport"
    ], 
    "deposit": [
        "sinDeposit", 
        "cashDeposit"
    ], 
    "nextDayEntity": [
        "spyResult"
    ], 
    "pathMaker": [
        "adultPathSelf", 
        "cityPathAdult", 
        "intelligentPathCity", 
        "intelligentPathMotherland", 
        "motherlandPathAdult", 
        "personBPathCity", 
        "personBPathMotherland", 
        "personDPathCity", 
        "personDPathMotherland"
    ], 
    "tower": [
        "choreTower", 
        "travelTower", 
        "cleanTower", 
        "cabTower", 
        "spyTower", 
        "paintTower", 
        "walkTower", 
        "understandTower", 
        "chessTower", 
        "planTower", 
        "thinkAboutTower", 
        "driveTower", 
        "disavowalTower", 
        "contactTower"
    ], 
    "personResourceButton": [
        "socialMineButton_personB", 
        "fantasyMineButton_personB", 
        "daydreamMineButton_personB", 
        "impressMineButton_personB", 
        "socialMineButton_personD", 
        "fantasyMineButton_personD", 
        "daydreamMineButton_personD", 
        "impressMineButton_personD"
    ], 
    "personCurrency": [
        "confesssion_personB", 
        "confesssion_personD", 
        "contact_personB", 
        "contact_personD", 
        "date_personB", 
        "date_personD", 
        "flirt_personB", 
        "flirt_personD", 
        "social_personB", 
        "social_personD"
    ], 
    "personTower": [
        "confessTower_personB", 
        "contactTower_personB", 
        "dateTower_personB", 
        "flirtTower_personB", 
        "talkTower_personB", 
        "confessTower_personD", 
        "contactTower_personD", 
        "dateTower_personD", 
        "flirtTower_personD", 
        "talkTower_personD"
    ], 
    "emotionSourceConstruction": [
        "nastyCommentMineConstruction", 
        "defineSelfMineConstruction", 
        "betrayalMineConstruction", 
        "threatMineConstruction", 
        "acceptFearMineConstruction", 
        "fearFlashMineConstruction", 
        "programmingMineConstruction", 
        "confessFearMineConstruction", 
        "lifeThreatMineConstruction", 
        "dateGoodMineConstruction", 
        "prideJoyMineConstruction", 
        "askOutSuccessMineConstruction", 
        "ringExciteMineConstruction", 
        "dateGreatMineConstruction", 
        "dateLuckyMineConstruction", 
        "missHomeMineConstruction", 
        "dateSadMineConstruction", 
        "consequenceSadMineConstruction", 
        "learnSadMineConstruction", 
        "shameMineConstruction", 
        "dateFailMineConstruction", 
        "disavowAngerMineConstruction", 
        "disavowSadnessMineConstruction", 
        "flashback_angerConstruction", 
        "flashback_joyConstruction", 
        "flashback_fearConstruction", 
        "flashback_sadnessConstruction"
    ], 
    "memoryTowerButton": [
        "applyButton", 
        "empathizeButton", 
        "rechargeButton", 
        "prepareButton"
    ]
}

var findKeyFor = function(csvId, key, val){
    return _.chain(csvIdentifiers[csvId])
    .filter(function(argKey){
        return (allArgs[argKey][key] == val);
    })
    .first()
    .value();
};

var updateArgs = function(args, defaultArgs){
    _.each(defaultArgs, function(val, key){
        args[key] = val;
    });
};

var uidManager = (function(){
    var uid = 0;
    return {
        getUid: function(){
            uid += 1;
            return uid;
        },
        setUid: function(newUid){
            uid = newUid;
        }
    };
})();

var makeEcs = function(key, args){
    if (ecs[key] === undefined){
        console.log('tried to build ' + key);
    }

    args = (args === undefined) ? {} : args;
    if (!args.uid){
        args.uid = uidManager.getUid();
    }
    
    var passedArgs = _.clone(args);
    
    var newEcs = ecs[key](args);
    
    newEcs.key = key;
    newEcs.passedArgs = passedArgs;
    newEcs.uid = args.uid;

    return newEcs;
};

var makeAllEcs = function(csvId){
    return _.map(csvIdentifiers[csvId], function(key){
        return makeEcs(key, {});
    });
};
