Merge branch 'master' into production

This commit is contained in:
Matt Lott 2015-01-15 11:09:56 -08:00
commit 0374c65c22
90 changed files with 1349 additions and 290 deletions

View file

@ -1,5 +1,5 @@
{me} = require 'core/auth'
AnalyticsLogEvent = require 'models/AnalyticsLogEvent'
SuperModel = require 'models/SuperModel'
debugAnalytics = false
@ -10,6 +10,7 @@ module.exports = class Tracker
window.tracker = @
@isProduction = document.location.href.search('codecombat.com') isnt -1
@identify()
@supermodel = new SuperModel()
identify: (traits) ->
console.log 'Would identify', traits if debugAnalytics
@ -61,19 +62,7 @@ module.exports = class Tracker
# https://segment.com/docs/integrations/mixpanel/
properties = properties or {}
# Log internally
# Skipping heavily logged actions we don't use internally
unless action in ['Simulator Result', 'Loaded World Map', 'Started Level Load', 'Finished Level Load', 'Homepage Loaded']
# Trimming properties we don't use internally
# TODO: delete internalProperites.level for 'Saw Victory' after 2/8/15. Should be using levelID instead.
internalProperties = _.cloneDeep properties
if action in ['Clicked Level', 'Inventory Play', 'Heard Sprite', 'Started Level', 'Saw Victory', 'Click Play', 'Choose Inventory']
delete internalProperties.category
delete internalProperties.label
console.log 'Tracking internal analytics event:', action, internalProperties, includeIntegrations if debugAnalytics
event = new AnalyticsLogEvent event: action, properties: internalProperties
event.save()
@trackEventInternal action, _.cloneDeep properties
console.log 'Would track analytics event:', action, properties, includeIntegrations if debugAnalytics
return unless me and @isProduction and analytics? and not me.isAdmin()
@ -85,6 +74,24 @@ module.exports = class Tracker
context.integrations[integration] = true
analytics?.track action, properties, context
trackEventInternal: (event, properties) =>
# Skipping heavily logged actions we don't use internally
unless event in ['Simulator Result', 'Started Level Load', 'Finished Level Load']
# Trimming properties we don't use internally
# TODO: delete internalProperites.level for 'Saw Victory' after 2/8/15. Should be using levelID instead.
if event in ['Clicked Level', 'Inventory Play', 'Heard Sprite', 'Started Level', 'Saw Victory', 'Click Play', 'Choose Inventory', 'Loaded World Map', 'Homepage Loaded', 'Change Hero']
delete properties.category
delete properties.label
else if event in ['Started Signup', 'Finished Signup', 'Login', 'Facebook Login', 'Google Login']
delete properties.category
console.log 'Tracking internal analytics event:', event, properties if debugAnalytics
request = @supermodel.addRequestResource 'log_event', {
url: '/db/analytics_log_event/-/log_event'
data: {event: event, properties: properties}
method: 'POST'
}, 0
request.load()
trackTiming: (duration, category, variable, label, samplePercentage=5) ->
# https://developers.google.com/analytics/devguides/collection/gajs/gaTrackingTiming

View file

@ -131,7 +131,7 @@ module.exports.kindaEqual = compare = (l, r) ->
else
return false
# Return UTC string "YYYY-MM-DD" for today + offset
# Return UTC string "YYYYMMDD" for today + offset
module.exports.getUTCDay = (offset=0) ->
day = new Date()
day.setDate(day.getUTCDate() + offset)
@ -140,7 +140,7 @@ module.exports.getUTCDay = (offset=0) ->
partMonth = "0" + partMonth if partMonth < 10
partDay = day.getUTCDate()
partDay = "0" + partDay if partDay < 10
"#{partYear}-#{partMonth}-#{partDay}"
"#{partYear}#{partMonth}#{partDay}"
# Fast, basic way to replace text in an element when you don't need much.
# http://stackoverflow.com/a/4962398/540620

View file

@ -220,8 +220,8 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
@notifyScriptStateChanged()
@scriptInProgress = true
@currentTimeouts = []
scriptLabel = "#{@levelID}: #{nextNoteGroup.scriptID} - #{nextNoteGroup.name}"
application.tracker?.trackEvent 'Script Started', {label: scriptLabel}, ['Google Analytics']
scriptLabel = "#{nextNoteGroup.scriptID} - #{nextNoteGroup.name}"
application.tracker?.trackEvent 'Script Started', {levelID: @levelID, label: scriptLabel, ls: @session?.get('_id')}, ['Google Analytics']
console.debug "SCRIPT: Starting note group '#{nextNoteGroup.name}'" if @debugScripts
for module in nextNoteGroup.modules
@processNote(note, nextNoteGroup) for note in module.startNotes()
@ -283,8 +283,8 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
return if @ending # kill infinite loops right here
@ending = true
return unless @currentNoteGroup?
scriptLabel = "#{@levelID}: #{@currentNoteGroup.scriptID} - #{@currentNoteGroup.name}"
application.tracker?.trackEvent 'Script Ended', {label: scriptLabel}, ['Google Analytics']
scriptLabel = "#{@currentNoteGroup.scriptID} - #{@currentNoteGroup.name}"
application.tracker?.trackEvent 'Script Ended', {levelID: @levelID, label: scriptLabel, ls: @session?.get('_id')}, ['Google Analytics']
console.debug "SCRIPT: Ending note group '#{@currentNoteGroup.name}'" if @debugScripts
clearTimeout(timeout) for timeout in @currentTimeouts
for module in @currentNoteGroup.modules

View file

@ -30,6 +30,7 @@ module.exports = Lank = class Lank extends CocoClass
thang: null
camera: null
showInvisible: false
preloadSounds: true
possessed: false
flipped: false
@ -84,8 +85,9 @@ module.exports = Lank = class Lank extends CocoClass
onThangTypeLoaded: ->
@stillLoading = false
for trigger, sounds of @thangType.get('soundTriggers') or {} when trigger isnt 'say'
AudioPlayer.preloadSoundReference sound for sound in sounds when sound
if @options.preloadSounds
for trigger, sounds of @thangType.get('soundTriggers') or {} when trigger isnt 'say'
AudioPlayer.preloadSoundReference sound for sound in sounds when sound
if @thangType.get('raster')
@actions = {}
@isRaster = true

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "العربية", englishDescription: "Arabi
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "العربية", englishDescription: "Arabi
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "български език", englishDescri
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "български език", englishDescri
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -6,12 +6,12 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
play: "Jugar" # The big play button that opens up the campaign view.
old_browser: "Uh oh, el teu navegador és massa antic per fer funcionar CodeCombat. Perdó!" # Warning that shows up on really old Firefox/Chrome/Safari
old_browser_suffix: "Pots probar-ho igualment, però el més segur és que no funcioni."
# ipad_browser: "Bad news: CodeCombat doesn't run on iPad in the browser. Good news: our native iPad app is awaiting Apple approval."
ipad_browser: "Males notícies: CodeCombat no funciona en el navegador d'iPad. Bones notícies: la nostre aplicació d'iPad està esperant l'aprovació d'Apple."
campaign: "Campanya"
for_beginners: "Per a principiants"
multiplayer: "Multijugador" # Not currently shown on home page
for_developers: "Per a Desenvolupadors" # Not currently shown on home page.
# or_ipad: "Or download for iPad"
or_ipad: "O descarrega-la per iPad"
nav:
play: "Nivells" # The top nav bar entry where players choose which levels to play
@ -53,12 +53,12 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
players: "jugadors" # Hover over a level on /play
hours_played: "hores de joc" # Hover over a level on /play
items: "Objectes" # Tooltip on item shop button from /play
# unlock: "Unlock" # For purchasing items and heroes
# confirm: "Confirm"
# owned: "Owned" # For items you own
# locked: "Locked"
# purchasable: "Purchasable" # For a hero you unlocked but haven't purchased
# available: "Available"
unlock: "Desbloquejar" # For purchasing items and heroes
confirm: "Confirmar"
owned: "En propietat" # For items you own
locked: "Bloquejat"
purchasable: "Comprable" # For a hero you unlocked but haven't purchased
available: "Disponible"
# skills_granted: "Skills Granted" # Property documentation details
heroes: "Herois" # Tooltip on hero shop button from /play
achievements: "Triomfs" # Tooltip on achievement list button from /play
@ -67,13 +67,13 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
next: "Següent" # Go from choose hero to choose inventory before playing a level
change_hero: "Canviar heroi" # Go back from choose inventory to choose hero
choose_inventory: "Equipar objectes"
# buy_gems: "Buy Gems"
# campaign_desert: "Desert Campaign"
# campaign_forest: "Forest Campaign"
buy_gems: "Compra Gemes"
campaign_desert: "Campanya del desert"
campaign_forest: "Campanya del bosc"
# campaign_dungeon: "Dungeon Campaign"
# subscription_required: "Subscription Required"
# free: "Free"
# subscribed: "Subscribed"
subscription_required: "Subscripció requerida"
free: "Gratuit"
subscribed: "Subscrit"
older_campaigns: "Campanyes antigues"
anonymous: "Jugador anònim"
level_difficulty: "Dificultat: "
@ -105,10 +105,10 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# authenticate_gplus: "Authenticate G+"
# load_profile: "Load G+ Profile"
# load_email: "Load G+ Email"
# finishing: "Finishing"
finishing: "Acabant"
# sign_in_with_facebook: "Sign in with Facebook"
# sign_in_with_gplus: "Sign in with G+"
# signup_switch: "Want to create an account?"
signup_switch: "Vols crear un compte?"
signup:
email_announcements: "Rebre anuncis via email"
@ -117,7 +117,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
log_in: "Iniciar sessió amb la teva contrasenya"
social_signup: "O, pots iniciar sesió desde Facebook o G+:"
required: "Neccesites iniciar sesió abans ."
# login_switch: "Already have an account?"
login_switch: "Ja tens compte?"
recover:
recover_account_title: "Recuperar Compte"
@ -125,12 +125,12 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
recovery_sent: "Correu de recuperació de contrasenya enviat."
items:
# primary: "Primary"
# secondary: "Secondary"
primary: "Primari"
secondary: "Secondari"
armor: "Armadura"
accessories: "Accessoris"
# misc: "Misc"
# books: "Books"
books: "Llibres"
common:
loading: "Carregant..."
@ -145,22 +145,22 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
fork: "Fork"
play: "Jugar" # When used as an action verb, like "Play next level"
retry: "Tornar a intentar"
# actions: "Actions"
# info: "Info"
# help: "Help"
actions: "Accions"
info: "Info"
help: "Ajuda"
watch: "Veure"
unwatch: "Amaga"
submit_patch: "Enviar pegat"
# submit_changes: "Submit Changes"
general:
# and: "and"
and: "i"
name: "Nom"
date: "Data"
body: "Cos"
version: "Versió"
# pending: "Pending"
# accepted: "Accepted"
pending: "Pendent"
accepted: "Acceptat"
# rejected: "Rejected"
# withdrawn: "Withdrawn"
# submitter: "Submitter"
@ -170,10 +170,10 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
version_history: "Historial de versions"
# version_history_for: "Version History for: "
# select_changes: "Select two changes below to see the difference."
# undo_prefix: "Undo"
# undo_shortcut: "(Ctrl+Z)"
undo_prefix: "Desfer"
undo_shortcut: "(Ctrl+Z)"
# redo_prefix: "Redo"
# redo_shortcut: "(Ctrl+Shift+Z)"
redo_shortcut: "(Ctrl+Shift+Z)"
# play_preview: "Play preview of current level"
result: "Resultat"
results: "Resultats"
@ -196,8 +196,8 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
medium: "Intermedi"
hard: "Difícil"
player: "Jugador"
# player_level: "Level" # Like player level 5, not like level: Dungeons of Kithgard
# warrior: "Warrior"
player_level: "Nivell" # Like player level 5, not like level: Dungeons of Kithgard
warrior: "Guerrer"
# ranger: "Ranger"
# wizard: "Wizard"
@ -220,7 +220,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
play_level:
done: "Fet"
home: "Inici" # Not used any more, will be removed soon.
# level: "Level" # Like "Level: Dungeons of Kithgard"
level: "Nivell" # Like "Level: Dungeons of Kithgard"
skip: "Ometre"
game_menu: "Menu de joc"
guide: "Guia"
@ -234,13 +234,13 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
failing: "Fallant"
action_timeline: "Cronologia d'accions"
# click_to_select: "Click on a unit to select it."
# control_bar_multiplayer: "Multiplayer"
control_bar_multiplayer: "Multijugador"
# control_bar_join_game: "Join Game"
# reload: "Reload"
# reload_title: "Reload All Code?"
# reload_really: "Are you sure you want to reload this level back to the beginning?"
# reload_confirm: "Reload All"
# victory: "Victory"
victory: "Victòria"
victory_title_prefix: ""
victory_title_suffix: " Complet"
victory_sign_up: "Inicia sessió per a desar el progressos"
@ -254,7 +254,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# victory_hour_of_code_done: "Are You Done?"
# victory_hour_of_code_done_yes: "Yes, I'm finished with my Hour of Code™!"
# victory_experience_gained: "XP Gained"
# victory_gems_gained: "Gems Gained"
victory_gems_gained: "Gemmes guanyades"
guide_title: "Guia"
# tome_minion_spells: "Your Minions' Spells" # Only in old-style levels.
# tome_read_only_spells: "Read-Only Spells" # Only in old-style levels.
@ -269,16 +269,16 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# tome_select_a_thang: "Select Someone for "
# tome_available_spells: "Available Spells"
tome_your_skills: "Les teves habilitats"
# tome_help: "Help"
tome_help: "Ajuda"
# tome_current_method: "Current Method"
# hud_continue_short: "Continue"
hud_continue_short: "Continua"
# code_saved: "Code Saved"
# skip_tutorial: "Skip (esc)"
keyboard_shortcuts: "Dreceres del teclat"
loading_ready: "Preparat!"
loading_start: "Comença el nivell"
# problem_alert_title: "Fix Your Code"
# problem_alert_help: "Help"
problem_alert_help: "Ajuda"
time_current: "Ara:"
time_total: "Maxim:"
time_goto: "Ves a:"
@ -322,7 +322,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
save_load_tab: "Desa/Carrega"
options_tab: "Opcions"
guide_tab: "Gui"
# guide_video_tutorial: "Video Tutorial"
guide_video_tutorial: "Video Tutorial"
# guide_tips: "Tips"
multiplayer_tab: "Multijugador"
# auth_tab: "Sign Up"
@ -336,31 +336,31 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
inventory:
choose_inventory: "Equipar objectes"
# equipped_item: "Equipped"
equipped_item: "Equipat"
# required_purchase_title: "Required"
# available_item: "Available"
available_item: "Disponible"
# restricted_title: "Restricted"
# should_equip: "(double-click to equip)"
# equipped: "(equipped)"
equipped: "(equipat)"
# locked: "(locked)"
# restricted: "(restricted in this level)"
# equip: "Equip"
# unequip: "Unequip"
equip: "Equipa"
unequip: "Desequipa"
# buy_gems:
# few_gems: "A few gems"
buy_gems:
few_gems: "Algunes gemmes"
# pile_gems: "Pile of gems"
# chest_gems: "Chest of gems"
# purchasing: "Purchasing..."
chest_gems: "Cofre de gemmes"
purchasing: "Comprant..."
# declined: "Your card was declined"
# retrying: "Server error, retrying."
# prompt_title: "Not Enough Gems"
# prompt_body: "Do you want to get more?"
# prompt_button: "Enter Shop"
prompt_button: "Entrar a la botiga"
# recovered: "Previous gems purchase recovered. Please refresh the page."
# subscribe:
# subscribe_title: "Subscribe"
subscribe:
subscribe_title: "Subscriu-te"
# unsubscribe: "Unsubscribe"
# levels: "Get more practice with bonus levels!"
# heroes: "More powerful heroes!"
@ -389,14 +389,14 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
io_blurb: "Senzill però obscur."
status: "Estat"
weapons: "Armes"
# weapons_warrior: "Swords - Short Range, No Magic"
weapons_warrior: "Espases - Curt abast, No Magic"
# weapons_ranger: "Crossbows, Guns - Long Range, No Magic"
# weapons_wizard: "Wands, Staffs - Long Range, Magic"
attack: "Dany" # Can also translate as "Attack"
health: "Salut"
speed: "Velocitat"
# regeneration: "Regeneration"
# range: "Range" # As in "attack or visual range"
regeneration: "Regeneració"
range: "Abast" # As in "attack or visual range"
# blocks: "Blocks" # As in "this shield blocks this much damage"
# backstab: "Backstab" # As in "this dagger does this much backstab damage"
skills: "Habilitats"
@ -404,21 +404,21 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# level_to_unlock: "Level to unlock:" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
# restricted_to_certain_heroes: "Only certain heroes can play this level."
# skill_docs:
skill_docs:
# writable: "writable" # Hover over "attack" in Your Skills while playing a level to see most of this
# read_only: "read-only"
# action_name: "name"
read_only: "Només lectura"
action_name: "nom"
# action_cooldown: "Takes"
# action_specific_cooldown: "Cooldown"
# action_damage: "Damage"
# action_range: "Range"
action_range: "Abast"
# action_radius: "Radius"
# action_duration: "Duration"
# example: "Example"
action_duration: "Duracció"
example: "Exemple"
# ex: "ex" # Abbreviation of "example"
# current_value: "Current Value"
# default_value: "Default value"
# parameters: "Parameters"
parameters: "Paràmetres"
# returns: "Returns"
# granted_by: "Granted by"
@ -608,15 +608,19 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
small: "Petit"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
small: "Petit"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
generate_terrain: "Generar Terreny"
more: "Més"
wiki: "Wiki"
live_chat: "Xat en directe"
# thang_main: "Main"
thang_main: "Principal"
# thang_spritesheets: "Spritesheets"
# thang_colors: "Colors"
# level_some_options: "Some Options?"
@ -630,9 +634,11 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
level_tab_thangs_all: "Tot"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
delete: "Esborrar"
duplicate: "Duplicar"
# rotate: "Rotate"
# stop_duplicate: "Stop Duplicate"
rotate: "Rotar"
level_settings_title: "Configuració"
level_component_tab_title: "Components actuals"
# level_component_btn_new: "Create New Component"
@ -644,7 +650,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# level_component_edit_title: "Edit Component"
# level_component_config_schema: "Config Schema"
level_component_settings: "Configuració"
# level_system_edit_title: "Edit System"
level_system_edit_title: "Editar sistema"
create_system_title: "Crea un nou sistema"
# new_component_title: "Create New Component"
new_component_field_system: "Sistema"
@ -815,7 +821,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
achievement: "Triomf"
category_contributor: "Contribuidor"
# category_ladder: "Ladder"
# category_level: "Level"
category_level: "Nivell"
category_miscellaneous: "Miscel·lània"
category_levels: "Nivells"
category_undefined: "Sense categoria"
@ -830,21 +836,21 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
account:
recently_played: "Ultimanent jugat"
no_recent_games: "No s'ha jugat en les ultimes setmanes."
# payments: "Payments"
payments: "Pagaments"
# purchased: "Purchased"
# subscription: "Subscription"
# service_apple: "Apple"
# service_web: "Web"
# paid_on: "Paid On"
service_apple: "Apple"
service_web: "Web"
paid_on: "Pagat a"
# service: "Service"
# price: "Price"
price: "Preu"
# gems: "Gems"
# active: "Active"
active: "Actiu"
# subscribed: "Subscribed"
# unsubscribed: "Unsubscribed"
# active_until: "Active Until"
# cost: "Cost"
# next_payment: "Next Payment"
cost: "Cost"
next_payment: "Següent pagament"
# card: "Card"
# status_unsubscribed_active: "You're not subscribed and won't be billed, but your account is still active for now."
# status_unsubscribed: "Get access to new levels, heroes, items, and bonus gems with a CodeCombat subscription!"
@ -894,7 +900,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# source_document: "Source Document"
document: "Documents"
# sprite_sheet: "Sprite Sheet"
# employers: "Employers"
employers: "Treballadors"
candidates: "Candidats"
# candidate_sessions: "Candidate Sessions"
# user_remark: "User Remark"
@ -1114,7 +1120,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# deprecation_warning_title: "Sorry, CodeCombat is not recruiting right now."
# deprecation_warning: "We are focusing on beginner levels instead of finding expert developers for the time being."
# hire_developers_not_credentials: "Hire developers, not credentials." # We are not actively recruiting right now, so there's no need to add new translations for the rest of this section.
# get_started: "Get Started"
get_started: "Comença"
# already_screened: "We've already technically screened all our candidates"
# filter_further: ", but you can also filter further:"
# filter_visa: "Visa"
@ -1151,7 +1157,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# candidate_top_skills: "Top Skills"
# candidate_years_experience: "Yrs Exp"
# candidate_last_updated: "Last Updated"
# candidate_who: "Who"
candidate_who: "Qui"
# featured_developers: "Featured Developers"
other_developers: "Altres desenvolupadors"
# inactive_developers: "Inactive Developers"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "čeština", englishDescription: "Czech", tr
revert: "Vrátit"
revert_models: "Vrátit modely"
pick_a_terrain: "Vybrat terén"
small: "Malý"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
grassy: "Travnatý"
small: "Malý"
# large: "Large"
fork_title: "Forkovat novou verzi"
fork_creating: "Vytváření Forku..."
generate_terrain: "Generování terénu"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "čeština", englishDescription: "Czech", tr
level_tab_thangs_all: "Všechny"
level_tab_thangs_conditions: "Výchozí prostředí"
level_tab_thangs_add: "Přidat Thangy"
# config_thang: "Double click to configure a thang"
delete: "Smazat"
duplicate: "Duplikovat"
# stop_duplicate: "Stop Duplicate"
rotate: "Otočit"
level_settings_title: "Nastavení"
level_component_tab_title: "Současné komponenty"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
# level_tab_thangs_all: "All"
level_tab_thangs_conditions: "Startbetingelser"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
level_settings_title: "Instillinger"
level_component_tab_title: "Nuværende komponenter"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Deutsch (Österreich)", englishDescription:
revert: "Zurücksetzen"
revert_models: "Models zurücksetzen."
pick_a_terrain: "Wähle ein Terrain"
small: "Klein"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
grassy: "Grasig"
small: "Klein"
# large: "Large"
fork_title: "Forke neue Version"
fork_creating: "Erzeuge Fork..."
generate_terrain: "Generiere Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Deutsch (Österreich)", englishDescription:
level_tab_thangs_all: "Alle"
level_tab_thangs_conditions: "Startbedingungen"
level_tab_thangs_add: "Thangs hinzufügen"
# config_thang: "Double click to configure a thang"
delete: "Löschen"
duplicate: "Duplizieren"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
level_settings_title: "Einstellungen"
level_component_tab_title: "Aktuelle Komponenten"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
revert: "Zurücksetzen"
revert_models: "Modelle zurücksetzen."
pick_a_terrain: "Wähle ein Terrain"
small: "Klein"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
grassy: "Grasig"
small: "Klein"
# large: "Large"
fork_title: "Forke neue Version"
fork_creating: "Erzeuge Fork..."
generate_terrain: "Generiere Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
level_tab_thangs_all: "Alle"
level_tab_thangs_conditions: "Startbedingungen"
level_tab_thangs_add: "Thangs hinzufügen"
# config_thang: "Double click to configure a thang"
delete: "Löschen"
duplicate: "Duplizieren"
# stop_duplicate: "Stop Duplicate"
rotate: "Drehen"
level_settings_title: "Einstellungen"
level_component_tab_title: "Aktuelle Komponenten"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Ελληνικά", englishDescription: "Gre
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "English (AU)", englishDescription: "English
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "English (AU)", englishDescription: "English
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "English (UK)", englishDescription: "English
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "English (UK)", englishDescription: "English
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "English (US)", englishDescription: "English
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "English (US)", englishDescription: "English
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@
revert: "Revert"
revert_models: "Revert Models"
pick_a_terrain: "Pick A Terrain"
small: "Small"
dungeon: "Dungeon"
indoor: "Indoor"
desert: "Desert"
grassy: "Grassy"
small: "Small"
large: "Large"
fork_title: "Fork New Version"
fork_creating: "Creating Fork..."
generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@
level_tab_thangs_all: "All"
level_tab_thangs_conditions: "Starting Conditions"
level_tab_thangs_add: "Add Thangs"
config_thang: "Double click to configure a thang"
delete: "Delete"
duplicate: "Duplicate"
stop_duplicate: "Stop Duplicate"
rotate: "Rotate"
level_settings_title: "Settings"
level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
revert: "Revertir"
revert_models: "Revertir Modelos"
pick_a_terrain: "Elije un Terreno"
small: "Pequeño"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
grassy: "Herboso"
small: "Pequeño"
# large: "Large"
fork_title: "Fork de Nueva Versión"
fork_creating: "Creando Fork..."
generate_terrain: "Generar terreno"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
level_tab_thangs_all: "Todo"
level_tab_thangs_conditions: "Condiciones Iniciales"
level_tab_thangs_add: "Agregar Thangs"
# config_thang: "Double click to configure a thang"
delete: "Borrar"
duplicate: "Duplicar"
# stop_duplicate: "Stop Duplicate"
rotate: "Rotar"
level_settings_title: "Opciones"
level_component_tab_title: "Componentes Actuales"

View file

@ -159,9 +159,9 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
date: "Fecha"
body: "Cuerpo"
version: "Versión"
# pending: "Pending"
# accepted: "Accepted"
# rejected: "Rejected"
pending: "Pendiente"
accepted: "Aceptado"
rejected: "Rechazado"
# withdrawn: "Withdrawn"
# submitter: "Submitter"
# submitted: "Submitted"
@ -174,7 +174,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
# undo_shortcut: "(Ctrl+Z)"
redo_prefix: "Rehacer"
# redo_shortcut: "(Ctrl+Shift+Z)"
# play_preview: "Play preview of current level"
play_preview: "Reproducir una vista previa del nivel actual"
result: "Resultado"
results: "Resultados"
description: "Descripción"
@ -199,7 +199,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
player_level: "Nivel" # Like player level 5, not like level: Dungeons of Kithgard
# warrior: "Warrior"
# ranger: "Ranger"
# wizard: "Wizard"
wizard: "Mago"
units:
second: "segundo"
@ -608,17 +608,21 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
revert: "Revertir"
revert_models: "Revertir Modelos"
pick_a_terrain: "Escoge un Terreno"
small: "Pequeño"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
grassy: "Cubierto de hierba"
small: "Pequeño"
# large: "Large"
fork_title: "Bifurcar nueva versión"
fork_creating: "Creando bifurcación..."
generate_terrain: "Generar Terreno"
more: "Más"
wiki: "Wiki"
live_chat: "Chat en directo"
# thang_main: "Main"
thang_main: "Principal"
# thang_spritesheets: "Spritesheets"
# thang_colors: "Colors"
thang_colors: "Colores"
level_some_options: "¿Algunas opciones?"
level_tab_thangs: "Objetos"
level_tab_scripts: "Scripts"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
level_tab_thangs_all: "Todo"
level_tab_thangs_conditions: "Condiciones de inicio"
level_tab_thangs_add: "Añadir Objetos"
# config_thang: "Double click to configure a thang"
delete: "Borrar"
duplicate: "Duplicar"
# stop_duplicate: "Stop Duplicate"
rotate: "Rotar"
level_settings_title: "Ajustes"
level_component_tab_title: "Componentes Actuales"
@ -674,7 +680,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
contribute:
page_title: "Colaborar"
# intro_blurb: "CodeCombat is 100% open source! Hundreds of dedicated players have helped us build the game into what it is today. Join us and write the next chapter in CodeCombat's quest to teach the world to code!"
intro_blurb: "¡CodeCombat es 100% código abierto! Cientos de dedicados jugadores nos han ayudado a convertir el juego en lo que es hoy en día. !Únete a nosotros y escribe el siguiente capítulo de CodeCombat en la aventura de enseñar al mundo a programar!"
alert_account_message_intro: "¡Hola!"
alert_account_message: "Para suscribirse a los mails de clase, necesitas estar logeado."
archmage_introduction: "Una de las mejores partes de desarrollar juegos es que combinan cosas muy diferentes. Gráficos, sonido, uso de redes en tiempo real, redes sociales y por supuesto mucho de los aspectos comunes de la programación, desde gestión de bases de datos a bajo nivel y administración de servidores hasta diseño de experiencia del usuario y creación de interfaces. Hay un montón de cosas por hacer y si eres un programador experimentado con interés en conocer lo que se cuece en la trastienda de CodeCombat, esta Clase puede ser la ideal para ti. Nos encantaría recibir tu ayuda para crear el mejor juego de programación de la historia."
@ -844,8 +850,8 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
# unsubscribed: "Unsubscribed"
# active_until: "Active Until"
# cost: "Cost"
# next_payment: "Next Payment"
# card: "Card"
next_payment: "Siguiente Pago"
card: "Tarjeta"
status_unsubscribed_active: "No estás suscrito y no seras facturado, pero tu cuenta sigue activa por ahora."
status_unsubscribed: "¡Obten acceso a nuevos niveles, heroes, artículos, y joyas adicionales con una suscripción a CodeCombat!"
@ -906,7 +912,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
clas: "Clasess"
play_counts: "Contador de Juegos"
feedback: "Apoyo"
# payment_info: "Payment Info"
payment_info: "Información de Pago"
delta:
added: "Añadido"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "فارسی", englishDescription: "Persian",
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "فارسی", englishDescription: "Persian",
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "suomi", englishDescription: "Finnish", tran
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "suomi", englishDescription: "Finnish", tran
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
revert: "Annuler"
revert_models: "Annuler les modèles"
pick_a_terrain: "Choisir un terrain"
small: "Petit"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
grassy: "Herbeux"
small: "Petit"
# large: "Large"
fork_title: "Fork une nouvelle version"
fork_creating: "Créer un Fork..."
generate_terrain: "Générer le terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
level_tab_thangs_all: "Tout"
level_tab_thangs_conditions: "Conditions de départ"
level_tab_thangs_add: "ajouter des Thangs"
# config_thang: "Double click to configure a thang"
delete: "Supprimer"
duplicate: "Dupliquer"
# stop_duplicate: "Stop Duplicate"
rotate: "Pivoter"
level_settings_title: "Paramètres"
level_component_tab_title: "Composants actuels"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Galego", englishDescription: "Galician", tr
revert: "Revertir"
revert_models: "Revertir Modelos"
pick_a_terrain: "Escolle un Terreno"
small: "Pequeno"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
grassy: "Cuberto de herba"
small: "Pequeno"
# large: "Large"
fork_title: "Bifurcar nova versión"
fork_creating: "Creando bifurcación..."
generate_terrain: "Xerar Terreo"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Galego", englishDescription: "Galician", tr
level_tab_thangs_all: "Todo"
level_tab_thangs_conditions: "Condicións de inicio"
level_tab_thangs_add: "Engadir Obxectos"
# config_thang: "Double click to configure a thang"
delete: "Borrar"
duplicate: "Duplicar"
# stop_duplicate: "Stop Duplicate"
rotate: "Rotar"
level_settings_title: "Axustes"
level_component_tab_title: "Compoñentes Actuais"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "עברית", englishDescription: "Hebrew",
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "עברית", englishDescription: "Hebrew",
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "मानक हिन्दी", englishDe
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "मानक हिन्दी", englishDe
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
small: "Kicsi"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
grassy: "Füves"
small: "Kicsi"
# large: "Large"
fork_title: "Új Verzió villára vétele"
# fork_creating: "Creating Fork..."
generate_terrain: "Terület generálása"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Bahasa Indonesia", englishDescription: "Ind
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Bahasa Indonesia", englishDescription: "Ind
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
level_tab_thangs_all: "Tutti"
level_tab_thangs_conditions: "Condizioni iniziali"
level_tab_thangs_add: "Aggiungi thang"
# config_thang: "Double click to configure a thang"
delete: "Cancella"
duplicate: "Duplica"
# stop_duplicate: "Stop Duplicate"
rotate: "Ruota"
level_settings_title: "Impostazioni"
level_component_tab_title: "Componenti esistenti"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "한국어", englishDescription: "Korean", t
revert: "되돌리기"
revert_models: "모델 되돌리기"
pick_a_terrain: "지형을 선택하세요."
small: "작게"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
grassy: "풀로 덮인"
small: "작게"
# large: "Large"
fork_title: "새 버전 가져오기"
fork_creating: "포크 생성중..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "한국어", englishDescription: "Korean", t
# level_tab_thangs_all: "All"
level_tab_thangs_conditions: "컨디션 시작"
level_tab_thangs_add: "Thangs 추가"
# config_thang: "Double click to configure a thang"
delete: "삭제"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
level_settings_title: "설정"
level_component_tab_title: "현재 요소들"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Македонски", englishDescription:
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Македонски", englishDescription:
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Bahasa Melayu", englishDescription: "Bahasa
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Bahasa Melayu", englishDescription: "Bahasa
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
revert: "Tilbakestill"
revert_models: "Tilbakestill Modeller"
pick_a_terrain: "Velg Terreng"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
generate_terrain: "Generer Terreng"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
level_tab_thangs_all: "Alle"
# level_tab_thangs_conditions: "Starting Conditions"
level_tab_thangs_add: "Legg til Thangs"
# config_thang: "Double click to configure a thang"
delete: "Slett"
duplicate: "Kopier"
# stop_duplicate: "Stop Duplicate"
rotate: "Rotér"
level_settings_title: "Innstillinger"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
revert: "Keer wijziging terug"
revert_models: "keer wijziging model terug"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
fork_title: "Kloon naar nieuwe versie"
fork_creating: "Kloon aanmaken..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
level_tab_thangs_all: "Alles"
level_tab_thangs_conditions: "Start Condities"
level_tab_thangs_add: "Voeg element toe"
# config_thang: "Double click to configure a thang"
delete: "Verwijder"
duplicate: "Dupliceer"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
level_settings_title: "Instellingen"
level_component_tab_title: "Huidige Componenten"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
revert: "Keer wijziging terug"
revert_models: "keer wijziging model terug"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
fork_title: "Kloon naar nieuwe versie"
fork_creating: "Kloon aanmaken..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
level_tab_thangs_all: "Alles"
level_tab_thangs_conditions: "Start Condities"
level_tab_thangs_add: "Voeg element toe"
# config_thang: "Double click to configure a thang"
delete: "Verwijder"
duplicate: "Dupliceer"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
level_settings_title: "Instellingen"
level_component_tab_title: "Huidige Componenten"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Norwegian Nynorsk", englishDescription: "No
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Norwegian Nynorsk", englishDescription: "No
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Norsk", englishDescription: "Norwegian", tr
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Norsk", englishDescription: "Norwegian", tr
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "język polski", englishDescription: "Polish
revert: "Przywróć"
revert_models: "Przywróć wersję"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "język polski", englishDescription: "Polish
# level_tab_thangs_all: "All"
level_tab_thangs_conditions: "Warunki początkowe"
level_tab_thangs_add: "Dodaj obiekty"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
level_settings_title: "Ustawienia"
level_component_tab_title: "Aktualne komponenty"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
revert: "Reverter"
revert_models: "Reverter Modelos"
pick_a_terrain: "Escolha um Terreno"
small: "Pequeno"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
grassy: "Gramado"
small: "Pequeno"
# large: "Large"
fork_title: "Realizar um Novo Fork"
fork_creating: "Criando Fork..."
generate_terrain: "Gerando Terreno"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Português do Brasil", englishDescription:
level_tab_thangs_all: "Tudo"
level_tab_thangs_conditions: "Condições de Início"
level_tab_thangs_add: "Adicionar Thangs"
# config_thang: "Double click to configure a thang"
delete: "Excluir"
duplicate: "Duplicar"
# stop_duplicate: "Stop Duplicate"
rotate: "Rotacionar"
level_settings_title: "Configurações"
level_component_tab_title: "Componentess Atuais"

View file

@ -159,10 +159,10 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
date: "Data"
body: "Corpo"
version: "Versão"
# pending: "Pending"
# accepted: "Accepted"
# rejected: "Rejected"
# withdrawn: "Withdrawn"
pending: "Pendentes"
accepted: "Aceitadas"
rejected: "Rejeitadas"
withdrawn: "Canceladas"
submitter: "Submissor"
submitted: "Submeteu"
commit_msg: "Mensagem da Submissão"
@ -197,9 +197,9 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
hard: "Difícil"
player: "Jogador"
player_level: "Nível" # Like player level 5, not like level: Dungeons of Kithgard
# warrior: "Warrior"
# ranger: "Ranger"
# wizard: "Wizard"
warrior: "Guerreiro"
ranger: "Arqueiro"
wizard: "Feiticeiro"
units:
second: "segundo"
@ -608,8 +608,12 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
revert: "Reverter"
revert_models: "Reverter Modelos"
pick_a_terrain: "Escolhe Um Terreno"
dungeon: "Masmorra"
indoor: "Interior"
desert: "Deserto"
grassy: "Relvado"
small: "Pequeno"
grassy: "Com Relva"
large: "Grande"
fork_title: "Bifurcar Nova Versão"
fork_creating: "A Criar Bifurcação..."
generate_terrain: "Gerar Terreno"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
level_tab_thangs_all: "Todos"
level_tab_thangs_conditions: "Condições Iniciais"
level_tab_thangs_add: "Adicionar Thangs"
config_thang: "Clica duas vezes para configurares uma thang"
delete: "Eliminar"
duplicate: "Duplicar"
stop_duplicate: "Parar de Duplicar"
rotate: "Rodar"
level_settings_title: "Configurações"
level_component_tab_title: "Componentes Atuais"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman
revert: "Revino la versiunea anterioară"
revert_models: "Resetează Modelele"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman
# level_tab_thangs_all: "All"
level_tab_thangs_conditions: "Condiți inițiale"
level_tab_thangs_add: "Adaugă Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
level_settings_title: "Setări"
level_component_tab_title: "Componente actuale"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
revert: "Откатить"
revert_models: "Откатить Модели"
pick_a_terrain: "Выберите ландшафт"
small: "Маленький"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
grassy: "Травянистый"
small: "Маленький"
# large: "Large"
fork_title: "Форк новой версии"
fork_creating: "Создание форка..."
generate_terrain: "Создать ландшафт"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
level_tab_thangs_all: "Все"
level_tab_thangs_conditions: "Начальные условия"
level_tab_thangs_add: "Добавить объект"
# config_thang: "Double click to configure a thang"
delete: "Удалить"
duplicate: "Дублировать"
# stop_duplicate: "Stop Duplicate"
rotate: "Повернуть"
level_settings_title: "Настройки"
level_component_tab_title: "Текущие компоненты"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "slovenčina", englishDescription: "Slovak",
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "slovenčina", englishDescription: "Slovak",
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "slovenščina", englishDescription: "Sloven
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "slovenščina", englishDescription: "Sloven
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr
revert: "Återställ"
revert_models: "Återställ modeller"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr
# level_tab_thangs_all: "All"
level_tab_thangs_conditions: "Startvillkor"
level_tab_thangs_add: "Lägg till enheter"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
level_settings_title: "Inställningar"
level_component_tab_title: "Nuvarande komponenter"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "ไทย", englishDescription: "Thai", tra
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "ไทย", englishDescription: "Thai", tra
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Türkçe", englishDescription: "Turkish", t
revert: "Geri al"
revert_models: "Önceki Modeller"
pick_a_terrain: "Bir Arazi Seçin"
small: "Küçük"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
grassy: "Çimli"
small: "Küçük"
# large: "Large"
fork_title: "Yeni Sürüm Çatalla"
fork_creating: "Çatal Oluşturuluyor..."
generate_terrain: "Arazi Oluştur"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Türkçe", englishDescription: "Turkish", t
level_tab_thangs_all: "Tüm"
level_tab_thangs_conditions: "Başlama Şartları"
level_tab_thangs_add: "Nesne Ekle"
# config_thang: "Double click to configure a thang"
delete: "Sil"
duplicate: "Kopyala"
# stop_duplicate: "Stop Duplicate"
rotate: "Döndür"
level_settings_title: "Ayarlar"
level_component_tab_title: "Geçerli Bileşenler"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Українська", englishDescription:
revert: "Повернутись"
revert_models: "Моделі повернення"
pick_a_terrain: "Обрати лашдшафт"
small: "Малий"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
grassy: "Подвір'я"
small: "Малий"
# large: "Large"
fork_title: "Форк нової версії"
fork_creating: "Створення форку..."
generate_terrain: "Згенерувати ландшафт"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Українська", englishDescription:
level_tab_thangs_all: "Усі"
level_tab_thangs_conditions: "Початковий статус"
level_tab_thangs_add: "Додати об'єкти"
# config_thang: "Double click to configure a thang"
delete: "Видалити"
duplicate: "Дублікат"
# stop_duplicate: "Stop Duplicate"
rotate: "Повернути"
level_settings_title: "Налаштування"
level_component_tab_title: "Поточні компоненти"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "اُردُو", englishDescription: "Urdu",
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "اُردُو", englishDescription: "Urdu",
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
revert: "还原"
revert_models: "还原模式"
pick_a_terrain: "选择地形"
small: "小的"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
grassy: "草地"
small: "小的"
# large: "Large"
fork_title: "派生新版本"
fork_creating: "正在执行派生..."
generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
level_tab_thangs_all: "所有"
level_tab_thangs_conditions: "启动条件"
level_tab_thangs_add: "增加物体"
# config_thang: "Double click to configure a thang"
delete: "删除"
duplicate: "复制"
# stop_duplicate: "Stop Duplicate"
rotate: "旋转"
level_settings_title: "设置"
level_component_tab_title: "目前所有组件"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "吴语", englishDescription: "Wuu (Simplifi
# revert: "Revert"
# revert_models: "Revert Models"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "吴语", englishDescription: "Wuu (Simplifi
# level_tab_thangs_all: "All"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# config_thang: "Double click to configure a thang"
# delete: "Delete"
# duplicate: "Duplicate"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"

View file

@ -608,8 +608,12 @@ module.exports = nativeDescription: "吳語", englishDescription: "Wuu (Traditio
revert: "還原"
revert_models: "還原模式"
# pick_a_terrain: "Pick A Terrain"
# small: "Small"
# dungeon: "Dungeon"
# indoor: "Indoor"
# desert: "Desert"
# grassy: "Grassy"
# small: "Small"
# large: "Large"
fork_title: "派生新版本"
fork_creating: "徠搭執行派生..."
# generate_terrain: "Generate Terrain"
@ -630,8 +634,10 @@ module.exports = nativeDescription: "吳語", englishDescription: "Wuu (Traditio
level_tab_thangs_all: "所有"
level_tab_thangs_conditions: "發動條件"
level_tab_thangs_add: "加物事"
# config_thang: "Double click to configure a thang"
delete: "刪除"
duplicate: "翻做"
# stop_duplicate: "Stop Duplicate"
# rotate: "Rotate"
level_settings_title: "設定"
level_component_tab_title: "能界所有組件"

View file

@ -6,7 +6,11 @@ AnalyticsLogEventSchema = c.object {
}
_.extend AnalyticsLogEventSchema.properties,
created: c.date({title: 'Created', readOnly: true})
u: c.objectId(links: [{rel: 'extra', href: '/db/user/{($)}'}])
e: {type: 'integer'}
p: {type: 'object'}
# TODO: Remove these legacy properties after we stop querying for them (probably 30 days, ~2/16/15)
user: c.objectId(links: [{rel: 'extra', href: '/db/user/{($)}'}])
event: {type: 'string'}
properties: {type: 'object'}

View file

@ -0,0 +1,18 @@
c = require './../schemas'
AnalyticsPerDaySchema = c.object {
title: 'Analytics per-day data'
description: 'Analytics data aggregated into per-day chunks.'
}
_.extend AnalyticsPerDaySchema.properties,
d: {type: 'string'} # yyyymmdd day, e.g. '20150123'
e: {type: 'integer'} # event (analytics string ID from analytics.strings)
l: {type: 'integer'} # level (analytics ID from analytics.strings)
f: {type: 'integer'} # filter (analytics ID from analytics.strings)
fv: {type: 'integer'} # filter value (analytics ID from analytics.strings)
c: {type: 'integer'} # count
c.extendBasicProperties AnalyticsPerDaySchema, 'analytics.perday'
module.exports = AnalyticsPerDaySchema

View file

@ -0,0 +1,13 @@
c = require './../schemas'
AnalyticsStringSchema = c.object {
title: 'Analytics String'
description: 'Maps strings to number IDs for improved performance.'
}
_.extend AnalyticsStringSchema.properties,
v: {type: 'string'} # value
c.extendBasicProperties AnalyticsStringSchema, 'analytics.string'
module.exports = AnalyticsStringSchema

View file

@ -78,13 +78,11 @@
span.sign-in-blurb(data-i18n="login.sign_in_with_facebook") Sign in with Facebook
.facebook-login-wrapper
.fb-login-button(data-show-faces="false", data-width="200", data-max-rows="1", data-scope="email")
// Google+ login causing script errors on IE10
if !isIE
.btn.btn-danger.btn-lg.btn-illustrated.network-login
img.network-logo(src="/images/pages/community/logo_g+.png", draggable="false")
span.sign-in-blurb(data-i18n="login.sign_in_with_gplus") Sign in with G+
.gplus-login-wrapper
.gplus-login-button#gplus-login-button
.btn.btn-danger.btn-lg.btn-illustrated.network-login
img.network-logo(src="/images/pages/community/logo_g+.png", draggable="false")
span.sign-in-blurb(data-i18n="login.sign_in_with_gplus") Sign in with G+
.gplus-login-wrapper
.gplus-login-button#gplus-login-button
.extra-pane
if mode === 'login'

View file

@ -40,7 +40,7 @@ block outer_content
#right-column
#campaign-view
#campaign-level-view.hidden
if campaignDropOffs
if campaignCompletions
button.btn.btn-default#analytics-button(title="Analytics", data-toggle="modal" data-target="#analytics-modal") Analytics
.modal.fade#analytics-modal(tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true")
.modal-dialog
@ -50,33 +50,25 @@ block outer_content
span(aria-hidden="true") &times;
h4.modal-title Analytics
.modal-body
if campaignDropOffs.startDay
if campaignDropOffs.endDay
div #{campaignDropOffs.startDay} to #{campaignDropOffs.endDay}
if campaignCompletions.startDay
if campaignCompletions.endDay
div #{campaignCompletions.startDay} to #{campaignCompletions.endDay}
else
div #{campaignDropOffs.startDay} to today
div #{campaignCompletions.startDay} to yesterday
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
thead
tr
td Level
td Started
td Dropped
td Drop %
td Finished
td Dropped
td Drop %
td Completion %
tbody
- for (var i = 0; i < campaignDropOffs.levels.length; i++)
- for (var i = 0; i < campaignCompletions.levels.length; i++)
tr
td= campaignDropOffs.levels[i].level
td= campaignDropOffs.levels[i].started
td= campaignDropOffs.levels[i].startDropped
td= campaignDropOffs.levels[i].startDropRate
td= campaignDropOffs.levels[i].finished
td= campaignDropOffs.levels[i].finishDropped
td= campaignDropOffs.levels[i].finishDropRate
td= campaignDropOffs.levels[i].completionRate
td= campaignCompletions.levels[i].level
td= campaignCompletions.levels[i].started
td= campaignCompletions.levels[i].finished
td= campaignCompletions.levels[i].completionRate
else
button.btn.btn-default.disabled#analytics-button Analytics Loading...

View file

@ -10,7 +10,7 @@ block modal-body-content
a(href="#")
div.choose-option(data-preset-type=preset.type, data-preset-size=size, style="background:url(/images/pages/editor/level/preset_"+preset.type+"_"+size+".jpg) no-repeat center; background-size: cover")
div.preset-size.name-label.capitalize
span(data-i18n="editor."+size) #{size}
span(data-i18n="editor."+size) #{size}
div.preset-name.capitalize
span(data-i18n="editor."+preset.type) #{preset.type}
.clearfix

View file

@ -4,7 +4,7 @@ button.btn#thangs-palette-toggle
span.icon-plus
.thangs-container.hide#all-thangs
h3(data-i18n="editor.level_tab_thangs_title") Current Thangs
#thangs-treema(title="Double click to configure a thang")
#thangs-treema(data-i18n="[title]editor.config_thang", title="Double click to configure a thang")
.world-container
#canvas-wrapper

View file

@ -1,16 +1,16 @@
.btn-group(data-toggle="buttons").status-buttons
label.btn.btn-default.pending
input(type="radio", name="status", value="pending", data-i18n="general.pending")
| Pending
input(type="radio", name="status", value="pending")
span(data-i18n="general.pending") Pending
label.btn.btn-default.accepted
input(type="radio", name="status", value="accepted", data-i18n="general.accepted")
| Accepted
input(type="radio", name="status", value="accepted")
span(data-i18n="general.accepted") Accepted
label.btn.btn-default.rejected
input(type="radio", name="status", value="rejected", data-i18n="general.rejected")
| Rejected
input(type="radio", name="status", value="rejected")
span(data-i18n="general.rejected") Rejected
label.btn.btn-default.withdrawn
input(type="radio", name="status", value="withdrawn", data-i18n="general.withdrawn")
| Withdrawn
input(type="radio", name="status", value="withdrawn")
span(data-i18n="general.withdrawn") Withdrawn
if patches.loading
p(data-i18n="common.loading") Loading...

View file

@ -13,7 +13,7 @@
ul.nav.nav-pills.nav-stacked
for category, index in itemCategories
li(class=index ? "" : "active")
li(class=index ? "" : "active", id=category + '-tab')
a.one-line(href="#item-category-" + category, data-toggle="tab")
img.tab-icon(src="/images/pages/play/modal/item-icon-"+category+".png", draggable="false")
span.big-font= itemCategoryNames[index]

View file

@ -47,7 +47,7 @@ module.exports = class CampaignEditorView extends RootView
@listenToOnce @levels, 'sync', @onFundamentalLoaded
@listenToOnce @achievements, 'sync', @onFundamentalLoaded
#_.delay @getCampaignCompletions, 1000 # Roughly never finishes loading, nearly kills server.
_.delay @getCampaignCompletions, 500
loadThangTypeNames: ->
# Load the names of the ThangTypes that this level's Treema nodes might want to display.
@ -132,7 +132,7 @@ module.exports = class CampaignEditorView extends RootView
getRenderData: ->
c = super()
c.campaign = @campaign
c.campaignDropOffs = @campaignDropOffs
c.campaignCompletions = @campaignCompletions
c
onClickSaveButton: ->
@ -241,26 +241,24 @@ module.exports = class CampaignEditorView extends RootView
@toSave.add achievement
getCampaignCompletions: =>
# Fetch last 7 days of campaign drop-off rates
# Fetch last 14 days of campaign drop-off rates
startDay = utils.getUTCDay -6
startDay = utils.getUTCDay -14
endDay = utils.getUTCDay -1
success = (data) =>
return if @destroyed
# API returns all the campaign data currently
@campaignDropOffs = data[@campaignHandle]
mapFn = (item) ->
item.startDropRate = (item.startDropped / item.started * 100).toFixed(2)
item.finishDropRate = (item.finishDropped / item.finished * 100).toFixed(2)
item.completionRate = (item.finished / item.started * 100).toFixed(2)
item
@campaignDropOffs.levels = _.map @campaignDropOffs.levels, mapFn, @
@campaignDropOffs.startDay = startDay
@campaignCompletions = levels: _.map data, mapFn, @
@campaignCompletions.startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
@campaignCompletions.endDay = "#{endDay[0..3]}-#{endDay[4..5]}-#{endDay[6..7]}"
@render()
# TODO: Why do we need this url dash?
request = @supermodel.addRequestResource 'campaign_completions', {
url: '/db/analytics_log_event/-/campaign_completions'
url: '/db/analytics_perday/-/campaign_completions'
data: {startDay: startDay, slug: @campaignHandle}
method: 'POST'
success: success

View file

@ -49,6 +49,7 @@ module.exports = class CampaignLevelView extends CocoView
getCommonLevelProblems: ->
# Fetch last 30 days of common level problems
startDay = utils.getUTCDay -29
startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
success = (data) =>
return if @destroyed
@ -66,7 +67,7 @@ module.exports = class CampaignLevelView extends CocoView
request.load()
getLevelCompletions: ->
# Fetch last 7 days of level completion counts
# Fetch last 14 days of level completion counts
success = (data) =>
return if @destroyed
data.sort (a, b) -> if a.created < b.created then 1 else -1
@ -76,11 +77,11 @@ module.exports = class CampaignLevelView extends CocoView
@levelCompletions = _.map data, mapFn, @
@render()
startDay = utils.getUTCDay -13
startDay = utils.getUTCDay -14
# TODO: Why do we need this url dash?
request = @supermodel.addRequestResource 'level_completions', {
url: '/db/analytics_log_event/-/level_completions'
url: '/db/analytics_perday/-/level_completions'
data: {startDay: startDay, slug: @levelSlug}
method: 'POST'
success: success
@ -88,13 +89,14 @@ module.exports = class CampaignLevelView extends CocoView
request.load()
getLevelPlaytimes: ->
# Fetch last 7 days of level average playtimes
# Fetch last 14 days of level average playtimes
success = (data) =>
return if @destroyed
@levelPlaytimes = data.sort (a, b) -> if a.created < b.created then 1 else -1
@render()
startDay = utils.getUTCDay -13
startDay = "#{startDay[0..3]}-#{startDay[4..5]}-#{startDay[6..7]}"
# TODO: Why do we need this url dash?
request = @supermodel.addRequestResource 'playtime_averages', {

View file

@ -625,9 +625,9 @@ module.exports = class ThangsTabView extends CocoView
onSpriteContextMenu: (e) ->
{clientX, clientY} = e.originalEvent.nativeEvent
if @addThangType
$('#duplicate a').html 'Stop Duplicate'
$('#duplicate a').html $.i18n.t 'editor.stop_duplicate'
else
$('#duplicate a').html 'Duplicate'
$('#duplicate a').html $.i18n.t 'editor.duplicate'
$('#contextmenu').css { position: 'fixed', left: clientX, top: clientY }
$('#contextmenu').show()

View file

@ -192,7 +192,7 @@ module.exports = class CampaignView extends RootView
@highlightElement '.level.next', delay: 500, duration: 60000, rotation: 0, sides: ['top']
if @editorMode
for level in @campaign.renderedLevels
for nextLevelOriginal in level.nextLevels
for nextLevelOriginal in level.nextLevels ? []
if nextLevel = _.find(@campaign.renderedLevels, original: nextLevelOriginal)
@createLine level.position, nextLevel.position
@applyCampaignStyles()

View file

@ -17,6 +17,11 @@ module.exports = class LevelDialogueView extends CocoView
'click': 'onClick'
'click a': 'onClickLink'
constructor: (options) ->
super options
@level = options.level
@sessionID = options.sessionID
onClick: (e) ->
Backbone.Mediator.publish 'tome:focus-editor', {}
@ -33,7 +38,7 @@ module.exports = class LevelDialogueView extends CocoView
$('body').addClass('dialogue-view-active')
@setMessage e.message, e.mood, e.responses
window.tracker?.trackEvent 'Heard Sprite', {message: e.message, label: e.message}, ['Google Analytics']
window.tracker?.trackEvent 'Heard Sprite', {message: e.message, label: e.message, ls: @sessionID}, ['Google Analytics']
onDialogueSoundCompleted: ->
@$el.removeClass 'speaking'

View file

@ -239,7 +239,7 @@ module.exports = class PlayLevelView extends RootView
@insertSubView new LevelFlagsView levelID: @levelID, world: @world if @$el.hasClass 'flags'
@insertSubView new GoldView {}
@insertSubView new HUDView {level: @level}
@insertSubView new LevelDialogueView {level: @level}
@insertSubView new LevelDialogueView {level: @level, sessionID: @session.id}
@insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session
@insertSubView new ProblemAlertView session: @session, level: @level, supermodel: @supermodel
worldName = utils.i18n @level.attributes, 'name'
@ -335,7 +335,8 @@ module.exports = class PlayLevelView extends RootView
if @options.realTimeMultiplayerSessionID?
Backbone.Mediator.publish 'playback:real-time-playback-waiting', {}
@realTimeMultiplayerContinueGame @options.realTimeMultiplayerSessionID
application.tracker?.trackEvent 'Started Level', category:'Play Level', levelID: @levelID
# TODO: Is it possible to create a Mongoose ObjectId for 'ls', instead of the string returned from get()?
application.tracker?.trackEvent 'Started Level', category:'Play Level', levelID: @levelID, ls: @session?.get('_id')
playAmbientSound: ->
return if @destroyed
@ -418,6 +419,7 @@ module.exports = class PlayLevelView extends RootView
level: @level.get('name')
label: @level.get('name')
levelID: @levelID
ls: @session?.get('_id')
application.tracker?.trackTiming victoryTime, 'Level Victory Time', @levelID, @levelID, 100
showVictory: ->

View file

@ -74,7 +74,7 @@ module.exports = class ProblemAlertView extends CocoView
@onWindowResize()
@render()
@onJiggleProblemAlert()
application.tracker?.trackEvent 'Show problem alert', levelID: @level.get('slug')
application.tracker?.trackEvent 'Show problem alert', {levelID: @level.get('slug'), ls: @session?.get('_id')}
onJiggleProblemAlert: ->
return unless @problem?
@ -90,7 +90,7 @@ module.exports = class ProblemAlertView extends CocoView
@onRemoveClicked()
onClickProblemAlertHelp: ->
application.tracker?.trackEvent 'Problem alert help clicked', levelID: @level.get('slug')
application.tracker?.trackEvent 'Problem alert help clicked', {levelID: @level.get('slug'), ls: @session?.get('_id')}
@openModalView new GameMenuModal showTab: 'guide', level: @level, session: @session, supermodel: @supermodel
onRemoveClicked: ->

View file

@ -14,6 +14,7 @@ module.exports = class LevelGuideView extends CocoView
constructor: (options) ->
@levelID = options.level.get('slug')
@sessionID = options.session.get('_id')
@helpVideos = options.level.get('helpVideos') ? []
@trackedHelpVideoStart = @trackedHelpVideoFinish = false
@ -85,12 +86,12 @@ module.exports = class LevelGuideView extends CocoView
onStartHelpVideo: ->
unless @trackedHelpVideoStart
window.tracker?.trackEvent 'Start help video', level: @levelID, style: @helpVideos[@helpVideosIndex].style
window.tracker?.trackEvent 'Start help video', level: @levelID, ls: @sessionID, style: @helpVideos[@helpVideosIndex].style
@trackedHelpVideoStart = true
onFinishHelpVideo: ->
unless @trackedHelpVideoFinish
window.tracker?.trackEvent 'Finish help video', level: @levelID, style: @helpVideos[@helpVideosIndex].style
window.tracker?.trackEvent 'Finish help video', level: @levelID, ls: @sessionID, style: @helpVideos[@helpVideosIndex].style
@trackedHelpVideoFinish = true
setupVideoPlayer: () ->

View file

@ -8,6 +8,8 @@ AudioPlayer = require 'lib/AudioPlayer'
utils = require 'core/utils'
BuyGemsModal = require 'views/play/modal/BuyGemsModal'
Purchase = require 'models/Purchase'
LayerAdapter = require 'lib/surface/LayerAdapter'
Lank = require 'lib/surface/Lank'
module.exports = class PlayHeroesModal extends ModalView
className: 'modal fade play-modal'
@ -39,9 +41,10 @@ module.exports = class PlayHeroesModal extends ModalView
@listenToOnce @heroes, 'sync', @onHeroesLoaded
@supermodel.loadCollection(@heroes, 'heroes')
@stages = {}
@layers = []
@session = options.session
@initCodeLanguageList options.hadEverChosenHero
@heroAnimationInterval = setInterval @animateHeroes, 2500
@heroAnimationInterval = setInterval @animateHeroes, 1000
onHeroesLoaded: ->
@formatHero hero for hero in @heroes.models
@ -155,41 +158,29 @@ module.exports = class PlayHeroesModal extends ModalView
@rerenderFooter()
return
canvas.show().prop width: @canvasWidth, height: @canvasHeight
builder = new SpriteBuilder(fullHero)
movieClip = builder.buildMovieClip(fullHero.get('actions').attack?.animation ? fullHero.get('actions').idle.animation)
movieClip.scaleX = movieClip.scaleY = canvas.prop('height') / 120 # Average hero height is ~110px tall at normal resolution
movieClip.regX = -fullHero.get('positions').registration.x
movieClip.regY = -fullHero.get('positions').registration.y
movieClip.x = canvas.prop('width') * 0.5
movieClip.y = canvas.prop('height') * 0.925 # This is where the feet go.
if fullHero.get('name') is 'Knight'
movieClip.scaleX *= 0.7
movieClip.scaleY *= 0.7
if fullHero.get('name') is 'Potion Master'
movieClip.scaleX *= 0.9
movieClip.scaleY *= 0.9
movieClip.regX *= 1.1
movieClip.regY *= 1.4
if fullHero.get('name') is 'Samurai'
movieClip.scaleX *= 0.7
movieClip.scaleY *= 0.7
movieClip.regX *= 1.2
movieClip.regY *= 1.35
if fullHero.get('name') is 'Librarian'
movieClip.regX *= 0.7
movieClip.regY *= 1.2
if fullHero.get('name') is 'Sorcerer'
movieClip.scaleX *= 0.9
movieClip.scaleY *= 0.9
movieClip.regX *= 1.15
movieClip.regY *= 1.3
stage = new createjs.Stage(canvas[0])
layer = new LayerAdapter({webGL:true})
@layers.push layer
layer.resolutionFactor = 8 # hi res!
layer.buildAsync = false
multiplier = 7
layer.scaleX = layer.scaleY = multiplier
lank = new Lank(fullHero, {preloadSounds: false})
layer.addLank(lank)
layer.on 'new-spritesheet', ->
#- maybe put some more normalization here?
m = multiplier
m *= 0.75 if fullHero.get('slug') in ['knight', 'samurai', 'librarian'] # these heroes are larger for some reason, shrink 'em
layer.container.scaleX = layer.container.scaleY = m
layer.container.children[0].x = 160/m
layer.container.children[0].y = 250/m
stage = new createjs.SpriteStage(canvas[0])
@stages[heroIndex] = stage
stage.addChild movieClip
stage.addChild layer.container
window.stage = stage
stage.update()
movieClip.loop = false
movieClip.gotoAndPlay 0
unless preloading
createjs.Ticker.addEventListener 'tick', stage
@playSelectionSound hero
@ -203,7 +194,8 @@ module.exports = class PlayHeroesModal extends ModalView
animateHeroes: =>
return unless @visibleHero
heroIndex = Math.max 0, _.findIndex(@heroes.models, ((hero) => hero.get('original') is @visibleHero.get('original')))
@stages[heroIndex]?.children?[0]?.gotoAndPlay? 0
animation = _.sample(['attack', 'idle', 'move_side', 'move_fore'])
@stages[heroIndex]?.children?[0]?.children?[0]?.gotoAndPlay? animation
playSelectionSound: (hero) ->
return if @$el.hasClass 'secret'
@ -333,4 +325,5 @@ module.exports = class PlayHeroesModal extends ModalView
for heroIndex, stage of @stages
createjs.Ticker.removeEventListener "tick", stage
stage.removeAllChildren()
layer.destroy() for layer in @layers
super()

View file

@ -7,6 +7,7 @@ BuyGemsModal = require 'views/play/modal/BuyGemsModal'
CocoCollection = require 'collections/CocoCollection'
ThangType = require 'models/ThangType'
LevelComponent = require 'models/LevelComponent'
Level = require 'models/Level'
Purchase = require 'models/Purchase'
utils = require 'core/utils'
@ -120,6 +121,9 @@ module.exports = class PlayItemsModal extends ModalView
@itemDetailsView = new ItemDetailsView()
@insertSubView(@itemDetailsView)
@$el.find("a[href='#item-category-armor']").click() # Start on armor tab, if it's there.
earnedLevels = me.get('earned')?.levels or []
if Level.levels['defense-of-plainswood'] not in earnedLevels
@$el.find('#misc-tab').hide()
onHidden: ->
super()

View file

@ -18,17 +18,27 @@ today = today.toISOString().substr(0, 10);
print("Today is " + today);
var todayMinus6 = new Date();
todayMinus6.setDate(todayMinus6.getUTCDate() - 6);
todayMinus6.setUTCDate(todayMinus6.getUTCDate() - 6);
var startDate = todayMinus6.toISOString().substr(0, 10) + "T00:00:00.000Z";
// startDate = "2014-12-31T00:00:00.000Z";
print("Start date is " + startDate)
// var endDate = "2015-01-06T00:00:00.000Z";
// print("End date is " + endDate)
function objectIdWithTimestamp(timestamp)
{
// Convert string date to Date object (otherwise assume timestamp is a date)
if (typeof(timestamp) == 'string') timestamp = new Date(timestamp);
// Convert date object to hex seconds since Unix epoch
var hexSeconds = Math.floor(timestamp/1000).toString(16);
// Create an ObjectId with that hex timestamp
var constructedObjectId = ObjectId(hexSeconds + "0000000000000000");
return constructedObjectId
}
var cursor = db['analytics.log.events'].find({
$and: [
{"created": { $gte: ISODate(startDate)}},
// {"created": { $lt: ISODate(endDate)}},
{_id: {$gte: objectIdWithTimestamp(ISODate(startDate))}},
{$or: [ {"event" : 'Started Level'}, {"event" : 'Saw Victory'}]}
]
});

View file

@ -0,0 +1,207 @@
// Insert per-day analytics into analytics.perdays collection
// Usage:
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
// Completion rates (funnels) are calculated like Mixpanel
// For a given date range, start count is the number of first steps (e.g. started a level)
// Finish count for the same start date is how many unique users finished the remaining steps in the following ~30 days
// https://mixpanel.com/help/questions/articles/how-are-funnels-calculated
// TODO: Why are Mixpanel level finish events significantly lower?
// TODO: dungeons-of-kithgard completion rate is 62% vs. 77%
// TODO: Similar start events, finish events off by 20% (5334 vs 6486)
// TODO: Are Mixpanel rates accounting for finishing steps likely to be completed in the future?
// TODO: Use Mixpanel export API to investigate
// TODO: Output documents updated/inserted
var scriptStartTime = new Date();
var analyticsStringCache = {};
function log(str) {
print(new Date().toISOString() + " " + str);
}
function objectIdWithTimestamp(timestamp) {
// Convert string date to Date object (otherwise assume timestamp is a date)
if (typeof(timestamp) == 'string') timestamp = new Date(timestamp);
// Convert date object to hex seconds since Unix epoch
var hexSeconds = Math.floor(timestamp/1000).toString(16);
// Create an ObjectId with that hex timestamp
var constructedObjectId = ObjectId(hexSeconds + "0000000000000000");
return constructedObjectId
}
function getAnalyticsString(str) {
if (analyticsStringCache[str]) return analyticsStringCache[str];
// Find existing string
var doc = db['analytics.strings'].findOne({v: str});
if (doc) {
analyticsStringCache[str] = doc._id;
return analyticsStringCache[str];
}
// Insert string
// http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/#auto-increment-optimistic-loop
doc = {v: str};
while (true) {
var cursor = db['analytics.strings'].find({}, {_id: 1}).sort({_id: -1}).limit(1);
var seq = cursor.hasNext() ? cursor.next()._id + 1 : 1;
doc._id = seq;
var results = db['analytics.strings'].insert(doc);
if (results.hasWriteError()) {
if ( results.writeError.code == 11000 /* dup key */ ) continue;
else throw new Error("ERROR: Unexpected error inserting data: " + tojson(results));
}
break;
}
// Find new string entry
doc = db['analytics.strings'].findOne({v: str});
if (doc) {
analyticsStringCache[str] = doc._id;
return analyticsStringCache[str];
}
throw new Error("ERROR: Did not find analytics.strings insert for: " + str);
}
function getLevelFunnelData(startDay, eventFunnel) {
if (!startDay || !eventFunnel || eventFunnel.length === 0) return {};
var startObj = objectIdWithTimestamp(ISODate(startDay + "T00:00:00.000Z"));
var queryParams = {$and: [{_id: {$gte: startObj}},{"event": {$in: eventFunnel}}]};
var cursor = db['analytics.log.events'].find(queryParams);
// Map ordering: level, user, event, created
var userDataMap = {};
while (cursor.hasNext()) {
var doc = cursor.next();
var created = doc._id.getTimestamp().toISOString().substring(0, 10);
var event = doc.event;
var properties = doc.properties;
var user = doc.user;
var level;
// TODO: Switch to properties.levelID for 'Saw Victory'
if (event === 'Saw Victory' && properties.level) level = properties.level.toLowerCase().replace(/ /g, '-');
else if (properties.levelID) level = properties.levelID
else continue
if (!userDataMap[level]) userDataMap[level] = {};
if (!userDataMap[level][user]) userDataMap[level][user] = {};
if (!userDataMap[level][user][event] || userDataMap[level][user][event].localeCompare(created) > 0) {
// if (userDataMap[level][user][event]) log("Found earlier date " + level + " " + event + " " + user + " " + userDataMap[level][user][event] + " " + created);
userDataMap[level][user][event] = created;
}
}
// Data: level, created, event
var levelFunnelData = {};
for (level in userDataMap) {
for (user in userDataMap[level]) {
// Find first event date
var funnelStartDay = null;
for (event in userDataMap[level][user]) {
var created = userDataMap[level][user][event];
if (!levelFunnelData[level]) levelFunnelData[level] = {};
if (!levelFunnelData[level][created]) levelFunnelData[level][created] = {};
if (!levelFunnelData[level][created][event]) levelFunnelData[level][created][event] = 0;
if (eventFunnel[0] === event) {
// First event gets attributed to current date
levelFunnelData[level][created][event]++;
funnelStartDay = created;
break;
}
}
if (funnelStartDay) {
// Add remaining funnel steps/events to first step's date
for (event in userDataMap[level][user]) {
if (!levelFunnelData[level][funnelStartDay][event]) levelFunnelData[level][funnelStartDay][event] = 0;
if (eventFunnel[0] != event) levelFunnelData[level][funnelStartDay][event]++;
}
// Zero remaining funnel events
for (var i = 1; i < eventFunnel.length; i++) {
var event = eventFunnel[i];
if (!levelFunnelData[level][funnelStartDay][event]) levelFunnelData[level][funnelStartDay][event] = 0;
}
}
// Else no start event in this date range
}
}
return levelFunnelData;
}
function insertEventCount(event, level, day, count) {
// analytics.perdays schema in server/analytics/AnalyticsPeryDay.coffee
day = day.replace(/-/g, '');
var eventID = getAnalyticsString(event);
var levelID = getAnalyticsString(level);
var filterID = getAnalyticsString('all');
var startObj = objectIdWithTimestamp(ISODate(startDay + "T00:00:00.000Z"));
var queryParams = {$and: [{d: day}, {e: eventID}, {l: levelID}, {f: filterID}]};
var doc = db['analytics.perdays'].findOne(queryParams);
if (doc && doc.c === count) return;
if (doc && doc.c !== count) {
// Update existing count, assume new one is more accurate
// log("Updating count in db for " + day + " " + event + " " + level + " " + doc.c + " => " + count);
var results = db['analytics.perdays'].update(queryParams, {$set: {c: count}});
if (results.nMatched !== 1 && results.nModified !== 1) {
log("ERROR: update count failed");
printjson(results);
}
}
else {
var insertDoc = {d: day, e: eventID, l: levelID, f: filterID, c: count};
var results = db['analytics.perdays'].insert(insertDoc);
if (results.nInserted !== 1) {
log("ERROR: insert event failed");
printjson(results);
printjson(insertDoc);
}
// else {
// log("Added " + day + " " + event + " " + count + " " + level);
// }
}
}
try {
// Look at last 30 days, same as Mixpanel
var numDays = 30;
var startDay = new Date();
today = startDay.toISOString().substr(0, 10);
startDay.setUTCDate(startDay.getUTCDate() - numDays);
startDay = startDay.toISOString().substr(0, 10);
var levelCompletionFunnel = ['Started Level', 'Saw Victory'];
log("Today is " + today);
log("Start day is " + startDay);
log("Funnel events are " + levelCompletionFunnel);
log("Getting level completion data...");
var levelCompletionData = getLevelFunnelData(startDay, levelCompletionFunnel);
log("Inserting aggregated level completion data...");
for (level in levelCompletionData) {
for (created in levelCompletionData[level]) {
if (today === created) continue; // Never save data for today because it's incomplete
for (event in levelCompletionData[level][created]) {
insertEventCount(event, level, created, levelCompletionData[level][created][event]);
}
}
}
}
catch(err) {
log("ERROR: " + err);
printjson(err);
}
log("Script runtime: " + (new Date() - scriptStartTime));

View file

@ -21,19 +21,28 @@ today = today.toISOString().substr(0, 10);
print("Today is " + today);
var todayMinus6 = new Date();
todayMinus6.setDate(todayMinus6.getUTCDate() - 6);
todayMinus6.setUTCDate(todayMinus6.getUTCDate() - 6);
var startDate = todayMinus6.toISOString().substr(0, 10) + "T00:00:00.000Z";
print("Start date is " + startDate)
// startDate = "2015-01-02T00:00:00.000Z";
// var endDate = "2015-01-09T00:00:00.000Z";
function objectIdWithTimestamp(timestamp)
{
// Convert string date to Date object (otherwise assume timestamp is a date)
if (typeof(timestamp) == 'string') timestamp = new Date(timestamp);
// Convert date object to hex seconds since Unix epoch
var hexSeconds = Math.floor(timestamp/1000).toString(16);
// Create an ObjectId with that hex timestamp
var constructedObjectId = ObjectId(hexSeconds + "0000000000000000");
return constructedObjectId
}
function getCompletionRates() {
print("Getting completion rates...");
var queryParams = {
$and: [
{"created": { $gte: ISODate(startDate)}},
// {"created": { $lt: ISODate(endDate)}},
// {$or: [ {"properties.level": {$exists: true}}, {"properties.levelID": {$exists: true}}]},
{_id: {$gte: objectIdWithTimestamp(ISODate(startDate))}},
{$or: [ {"event" : 'Started Level'}, {"event" : 'Saw Victory'}]}
]
};

View file

@ -80,9 +80,103 @@ class SetupFactory(object):
class MacSetup(SetupFactory):
def setup(self):
super(self.__class__, self).setup()
class WinSetup(SetupFactory):
def setup(self):
super(self.__class__, self).setup()
class LinuxSetup(SetupFactory):
def setup(self):
self.distroSetup()
super(self.__class__, self).setup()
def detectDistro(self):
distro_checks = {
"arch": "/etc/arch-release",
"ubuntu": "/etc/lsb-release"
}
for distro, path in distro_checks.items():
if os.path.exists(path):
return(distro)
def distroSetup(self):
distro = self.detectDistro()
if distro == "arch":
print("Arch Linux detected. Would you like to install \n"
"NodeJS and MongoDB via pacman? [y/N]")
if raw_input().lower() in ["y", "yes"]:
try:
subprocess.check_call(["pacman", "-S",
"nodejs", "mongodb",
"--noconfirm"])
except subprocess.CalledProcessError as err:
print("Installation failed. Retry, Continue, or "
"Abort? [r/c/A]")
answer = raw_input().lower()
if answer in ["r", "retry"]:
return(self.distroSetup())
elif answer in ["c", "continue"]:
return()
else:
exit(1)
else:
#try:
#print("Enabling and starting MongoDB in systemd.")
#subprocess.check_call(["systemctl", "enable",
# "mongodb.service"])
#subprocess.check_call(["systemctl", "start",
# "mongodb.service"])
#print("Node and Mongo installed. Continuing.")
#except subprocess.CalledProcessError as err:
#print("Mongo failed to start. Aborting")
#exit(1)
if distro == "ubuntu":
print("Ubuntu installation detected. Would you like to install \n"
"NodeJS and MongoDB via apt-get? [y/N]")
if raw_input().lower() in ["y", "yes"]:
print("Adding repositories for MongoDB and NodeJS...")
try:
subprocess.check_call(["apt-key", "adv",
"--keyserver",
"hkp://keyserver.ubuntu.com:80",
"--recv", "7F0CEB10"])
subprocess.check_call(["add-apt-repository",
"deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen"])
subprocess.check_call("curl -sL "
"https://deb.nodesource.com/setup"
" | bash", shell=True)
subprocess.check_call(["apt-get", "update"])
except subprocess.CalledProcessError as err:
print("Adding repositories failed. Retry, Install without"
"adding \nrepositories, Skip apt-get installation, "
"or Abort? [r/i/s/A]")
answer = raw_input().lower()
if answer in ["r", "retry"]:
return(self.distroSetup())
elif answer in ["i", "install"]:
pass
elif answer in ["s", "skip"]:
return()
else:
exit(1)
else:
try:
print("Repositories added successfully. Installing NodeJS and MongoDB.")
subprocess.check_call(["apt-get", "install",
"nodejs", "mongodb-org",
"build-essential", "-y"])
except subprocess.CalledProcessError as err:
print("Installation via apt-get failed. \nContinue "
"with manual installation, or Abort? [c/A]")
if raw_input().lower() in ["c", "continue"]:
return()
else:
exit(1)
else:
print("NodeJS and MongoDB installed successfully. "
"Staring MongoDB.")
#try:
#subprocess.check_call(["service", "mongod", "start"])
#except subprocess.CalledProcessError as err:
#print("Mongo failed to start. Aborting.")
#exit(1)

View file

@ -4,6 +4,7 @@ import configuration
import errors
import subprocess
import os
import sys
from which import which
#git clone https://github.com/nwinter/codecombat.git coco
class RepositoryInstaller():
@ -64,7 +65,14 @@ class RepositoryInstaller():
#TODO: "Replace npm with more robust package
#npm_location = self.config.directory.bin_directory + os.sep + "node" + os.sep + "bin" + os.sep + "npm"
npm_location = u"npm"
return_code = subprocess.call([npm_location,u"install"],cwd=self.config.directory.root_dir + os.sep + u"coco")
if sys.version_info[0] == 2:
py_cmd = "python"
else:
py_cmd = subprocess.check_output(['which', 'python2'])
return_code = subprocess.call([npm_location, u"install",
"--python=" + py_cmd],
cwd=self.config.directory.root_dir +
os.sep + u"coco")
if return_code:
raise errors.CoCoError(u"Failed to install node packages")
else:

View file

@ -2,10 +2,15 @@ mongoose = require 'mongoose'
plugins = require '../plugins/plugins'
AnalyticsLogEventSchema = new mongoose.Schema({
created:
type: Date
'default': Date.now
u: mongoose.Schema.Types.ObjectId
e: Number # event analytics.string ID
p: mongoose.Schema.Types.Mixed
# TODO: Remove these legacy properties after we stop querying for them (probably 30 days, ~2/16/15)
user: mongoose.Schema.Types.ObjectId
event: String
properties: mongoose.Schema.Types.Mixed
}, {strict: false})
AnalyticsLogEventSchema.index({event: 1, created: -1})
AnalyticsLogEventSchema.index({event: 1, _id: 1})
module.exports = AnalyticsLogEvent = mongoose.model('analytics.log.event', AnalyticsLogEventSchema)

View file

@ -0,0 +1,13 @@
mongoose = require 'mongoose'
AnalyticsPerDaySchema = new mongoose.Schema({
d: {type: String} # yyyymmdd day, e.g. '20150123'
e: {type: Number} # event (analytics string ID from analytics.strings)
l: {type: Number} # level (analytics string ID from analytics.strings)
f: {type: Number} # filter (analytics string ID from analytics.strings)
fv: {type: Number} # filter value (analytics string ID from analytics.strings)
c: {type: Number} # count
}, {strict: false})
# TODO: Why can't we query against a collection with caps, like 'analytics.perDay'?
module.exports = AnalyticsPerDay = mongoose.model('analytics.perday', AnalyticsPerDaySchema)

View file

@ -0,0 +1,12 @@
mongoose = require 'mongoose'
# Auto-incrementing number _id
# http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/#auto-increment-optimistic-loop
# TODO: Why strict:false?
AnalyticsStringSchema = new mongoose.Schema({
_id: {type: Number}
v: {type: String}
}, {strict: false})
module.exports = AnalyticsString = mongoose.model('analytics.string', AnalyticsStringSchema)

View file

@ -1,13 +1,17 @@
log = require 'winston'
mongoose = require 'mongoose'
utils = require '../lib/utils'
AnalyticsLogEvent = require './AnalyticsLogEvent'
Campaign = require '../campaigns/Campaign'
Level = require '../levels/Level'
Handler = require '../commons/Handler'
log = require 'winston'
class AnalyticsLogEventHandler extends Handler
modelClass: AnalyticsLogEvent
jsonSchema: require '../../app/schemas/models/analytics_log_event'
editableProperties: [
'e'
'p'
'event'
'properties'
]
@ -17,14 +21,118 @@ class AnalyticsLogEventHandler extends Handler
makeNewInstance: (req) ->
instance = super(req)
instance.set('u', req.user._id)
# TODO: Remove 'user' after we stop querying for it (probably 30 days, ~2/16/15)
instance.set('user', req.user._id)
instance
getByRelationship: (req, res, args...) ->
return @getLevelCompletionsBySlug(req, res) if args[1] is 'level_completions'
return @getCampaignCompletionsBySlug(req, res) if args[1] is 'campaign_completions'
return @logEvent(req, res) if args[1] is 'log_event'
# TODO: Remove these APIs
# return @getLevelCompletionsBySlug(req, res) if args[1] is 'level_completions'
# return @getCampaignCompletionsBySlug(req, res) if args[1] is 'campaign_completions'
super(arguments...)
logEvent: (req, res) ->
# Converts strings to string IDs where possible, and logs the event
user = req.user._id
event = req.query.event or req.body.event
properties = req.query.properties or req.body.properties
@sendSuccess res # Return request immediately
saveDoc = (eventID, slimProperties) ->
doc = new AnalyticsLogEvent
u: user
e: eventID
p: slimProperties
# TODO: Remove these legacy properties after we stop querying for them (probably 30 days, ~2/16/15)
user: user
event: event
properties: properties
doc.save()
utils.getAnalyticsStringID event, (eventID) ->
if eventID > 0
# TODO: properties slimming is pretty ugly
slimProperties = _.cloneDeep properties
if event in ['Clicked Level', 'Show problem alert', 'Started Level', 'Saw Victory', 'Problem alert help clicked', 'Spell palette help clicked']
delete slimProperties.level if event is 'Saw Victory'
properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
if slimProperties.levelID?
# levelID: string => l: string ID
utils.getAnalyticsStringID slimProperties.levelID, (levelStringID) ->
if levelStringID > 0
delete slimProperties.levelID
slimProperties.l = levelStringID
saveDoc eventID, slimProperties
return
else if event in ['Script Started', 'Script Ended']
properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
if slimProperties.levelID? and slimProperties.label?
# levelID: string => l: string ID
# label: string => lb: string ID
utils.getAnalyticsStringID slimProperties.levelID, (levelStringID) ->
if levelStringID > 0
delete slimProperties.levelID
slimProperties.l = levelStringID
utils.getAnalyticsStringID slimProperties.label, (labelStringID) ->
if labelStringID > 0
delete slimProperties.label
slimProperties.lb = labelStringID
saveDoc eventID, slimProperties
return
else if event is 'Heard Sprite'
properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
if slimProperties.message?
# message: string => m: string ID
utils.getAnalyticsStringID slimProperties.message, (messageStringID) ->
if messageStringID > 0
delete slimProperties.message
slimProperties.m = messageStringID
saveDoc eventID, slimProperties
return
else if event in ['Start help video', 'Finish help video']
properties.ls = mongoose.Types.ObjectId properties.ls if properties.ls
slimProperties.ls = mongoose.Types.ObjectId slimProperties.ls if slimProperties.ls
if slimProperties.level and slimProperties.style?
# level: string => l: string ID
# style: string => s: string ID
utils.getAnalyticsStringID slimProperties.level, (levelStringID) ->
if levelStringID > 0
delete slimProperties.level
slimProperties.l = levelStringID
utils.getAnalyticsStringID slimProperties.style, (styleStringID) ->
if styleStringID > 0
delete slimProperties.style
slimProperties.s = styleStringID
saveDoc eventID, slimProperties
return
else if event is 'Show subscription modal'
delete properties.category
delete slimProperties.category
if slimProperties.label?
# label: string => lb: string ID
utils.getAnalyticsStringID slimProperties.label, (labelStringID) ->
if labelStringID > 0
delete slimProperties.label
slimProperties.lb = labelStringID
if slimProperties.level?
# level: string => l: string ID
utils.getAnalyticsStringID slimProperties.level, (levelStringID) ->
if levelStringID > 0
delete slimProperties.level
slimProperties.l = levelStringID
saveDoc eventID, slimProperties
return
saveDoc eventID, slimProperties
return
saveDoc eventID, slimProperties
else
log.warn "Unable to get analytics string ID for " + event
getLevelCompletionsBySlug: (req, res) ->
# Returns an array of per-day level starts and finishes
# Parameters:
@ -60,8 +168,8 @@ class AnalyticsLogEventHandler extends Handler
queryParams = {$and: [
{$or: [{"event" : 'Started Level'}, {"event" : 'Saw Victory'}]}
]}
queryParams["$and"].push created: {$gte: new Date(startDay + "T00:00:00.000Z")} if startDay?
queryParams["$and"].push created: {$lt: new Date(endDay + "T00:00:00.000Z")} if endDay?
queryParams["$and"].push _id: {$gte: utils.objectIdFromTimestamp(startDay + "T00:00:00.000Z")} if startDay?
queryParams["$and"].push _id: {$lt: utils.objectIdFromTimestamp(endDay + "T00:00:00.000Z")} if endDay?
# Query stream is better for large results
# http://mongoosejs.com/docs/api.html#query_Query-stream
@ -221,8 +329,8 @@ class AnalyticsLogEventHandler extends Handler
userProgression = {}
queryParams = {$and: [{$or: [ {"event" : 'Started Level'}, {"event" : 'Saw Victory'}]}]}
queryParams["$and"].push created: {$gte: new Date(startDay + "T00:00:00.000Z")} if startDay?
queryParams["$and"].push created: {$lt: new Date(endDay + "T00:00:00.000Z")} if endDay?
queryParams["$and"].push _id: {$gte: utils.objectIdFromTimestamp(startDay + "T00:00:00.000Z")} if startDay?
queryParams["$and"].push _id: {$lt: utils.objectIdFromTimestamp(endDay + "T00:00:00.000Z")} if endDay?
# Query stream is better for large results
# http://mongoosejs.com/docs/api.html#query_Query-stream

View file

@ -0,0 +1,193 @@
AnalyticsPerDay = require './AnalyticsPerDay'
AnalyticsString = require './AnalyticsString'
Campaign = require '../campaigns/Campaign'
Level = require '../levels/Level'
Handler = require '../commons/Handler'
log = require 'winston'
class AnalyticsPerDayHandler extends Handler
modelClass: AnalyticsPerDay
jsonSchema: require '../../app/schemas/models/analytics_perday'
hasAccess: (req) ->
req.method in ['GET'] or req.user?.isAdmin()
getByRelationship: (req, res, args...) ->
return @getCampaignCompletionsBySlug(req, res) if args[1] is 'campaign_completions'
return @getLevelCompletionsBySlug(req, res) if args[1] is 'level_completions'
super(arguments...)
getCampaignCompletionsBySlug: (req, res) ->
# Send back an array of level starts and finishes
# Parameters:
# slug - campaign slug
# startDay - Inclusive, optional, YYYYMMDD e.g. '20141214'
# endDay - Exclusive, optional, YYYYMMDD e.g. '20141216'
campaignSlug = req.query.slug or req.body.slug
startDay = req.query.startDay or req.body.startDay
endDay = req.query.endDay or req.body.endDay
# log.warn "campaign_completions campaignSlug='#{campaignSlug}' startDay=#{startDay} endDay=#{endDay}"
return @sendSuccess res, [] unless campaignSlug?
# Cache results in app server memory for 1 day
@campaignCompletionsCache ?= {}
@campaignCompletionsCachedSince ?= new Date()
if (new Date()) - @campaignCompletionsCachedSince > 86400 * 1000
@campaignCompletionsCache = {}
@campaignCompletionsCachedSince = new Date()
cacheKey = campaignSlug
cacheKey += 's' + startDay if startDay?
cacheKey += 'e' + endDay if endDay?
return @sendSuccess res, campaignDropOffs if campaignDropOffs = @campaignCompletionsCache[cacheKey]
getCompletions = (orderedLevelSlugs, levelStringIDSlugMap) =>
# 3. Send back an array of level starts and finishes
# Input:
# orderedLevelSlugs - Ordered list of level slugs, used for sorting results
# levelStringIDSlugMap - Maps level string IDs to level slugs
campaignLevelIDs = Object.keys(levelStringIDSlugMap)
AnalyticsString.find({v: {$in: ['Started Level', 'Saw Victory', 'all']}}).exec (err, documents) =>
if err? then return @sendDatabaseError res, err
for doc in documents
startEventID = doc._id if doc.v is 'Started Level'
finishEventID = doc._id if doc.v is 'Saw Victory'
filterEventID = doc._id if doc.v is 'all'
return @sendSuccess res, [] unless startEventID? and finishEventID? and filterEventID?
queryParams = {$and: [
{$or: [{e: startEventID}, {e: finishEventID}]},
{f: filterEventID},
{l: {$in: campaignLevelIDs}}
]}
queryParams["$and"].push {d: {$gte: startDay}} if startDay?
queryParams["$and"].push {d: {$lt: endDay}} if endDay?
AnalyticsPerDay.find(queryParams).exec (err, documents) =>
if err? then return @sendDatabaseError res, err
levelEventCounts = {}
for doc in documents
levelEventCounts[doc.l] ?= {}
levelEventCounts[doc.l][doc.e] ?= 0
levelEventCounts[doc.l][doc.e] += doc.c
completions = []
for levelID of levelEventCounts
completions.push
level: levelStringIDSlugMap[levelID]
started: levelEventCounts[levelID][startEventID]
finished: levelEventCounts[levelID][finishEventID]
completions.sort (a, b) -> orderedLevelSlugs.indexOf(a.level) - orderedLevelSlugs.indexOf(b.level)
@campaignCompletionsCache[cacheKey] = completions
@sendSuccess res, completions
getLevelData = (campaignLevels) =>
# 2. Get ordered level slugs and string ID to level slug mappping
# Input:
# campaignLevels - array of Level IDs
queryParams = {original: {$in: campaignLevels}, "version.isLatestMajor": true, "version.isLatestMinor": true}
Level.find(queryParams).exec (err, documents) =>
if err? then return @sendDatabaseError res, err
# Save original level ID and slug in array for sorting
campaignOriginalSlugs = []
for doc in documents
campaignOriginalSlugs.push
slug: doc.get('name').toLowerCase().replace new RegExp(' ', 'g'), '-'
original: doc.get('original').toString()
# Sort slugs against original levels from campaign
campaignOriginalSlugs.sort (a, b) ->
if campaignLevels.indexOf(a.original) < campaignLevels.indexOf(b.original) then -1 else 1
# Lookup analytics string IDs for level slugs
orderedLevelSlugs = []
orderedLevelSlugs.push item.slug for item in campaignOriginalSlugs
AnalyticsString.find({v: {$in: orderedLevelSlugs}}).exec (err, documents) =>
if err? then return @sendDatabaseError res, err
levelStringIDSlugMap = {}
levelStringIDSlugMap[doc._id] = doc.v for doc in documents
getCompletions orderedLevelSlugs, levelStringIDSlugMap
# 1. Get campaign levels
Campaign.find({slug: campaignSlug}).exec (err, documents) =>
if err? then return @sendDatabaseError res, err
campaignLevels = []
campaignLevels.push level for level of doc.get('levels') for doc in documents
getLevelData campaignLevels
getLevelCompletionsBySlug: (req, res) ->
# Returns an array of per-day starts and finishes for given level
# Parameters:
# slug - level slug
# startDay - Inclusive, optional, YYYYMMDD e.g. '20141214'
# endDay - Exclusive, optional, YYYYMMDD e.g. '20141216'
# TODO: Code is similar to getCampaignCompletionsBySlug
levelSlug = req.query.slug or req.body.slug
startDay = req.query.startDay or req.body.startDay
endDay = req.query.endDay or req.body.endDay
return @sendSuccess res, [] unless levelSlug?
# log.warn "level_completions levelSlug='#{levelSlug}' startDay=#{startDay} endDay=#{endDay}"
# Cache results in app server memory for 1 day
@levelCompletionsCache ?= {}
@levelCompletionsCachedSince ?= new Date()
if (new Date()) - @levelCompletionsCachedSince > 86400 * 1000
@levelCompletionsCache = {}
@levelCompletionsCachedSince = new Date()
cacheKey = levelSlug
cacheKey += 's' + startDay if startDay?
cacheKey += 'e' + endDay if endDay?
return @sendSuccess res, levelCompletions if levelCompletions = @levelCompletionsCache[cacheKey]
AnalyticsString.find({v: {$in: ['Started Level', 'Saw Victory', 'all', levelSlug]}}).exec (err, documents) =>
if err? then return @sendDatabaseError res, err
for doc in documents
startEventID = doc._id if doc.v is 'Started Level'
finishEventID = doc._id if doc.v is 'Saw Victory'
filterEventID = doc._id if doc.v is 'all'
levelID = doc._id if doc.v is levelSlug
return @sendSuccess res, [] unless startEventID? and finishEventID? and filterEventID? and levelID?
queryParams = {$and: [{$or: [{e: startEventID}, {e: finishEventID}]},{f: filterEventID},{l: levelID}]}
queryParams["$and"].push {d: {$gte: startDay}} if startDay?
queryParams["$and"].push {d: {$lt: endDay}} if endDay?
AnalyticsPerDay.find(queryParams).exec (err, documents) =>
if err? then return @sendDatabaseError res, err
dayEventCounts = {}
for doc in documents
day = doc.get('d')
eventID = doc.get('e')
count = doc.get('c')
dayEventCounts[day] ?= {}
dayEventCounts[day][eventID] = count
completions = []
for day of dayEventCounts
for eventID of dayEventCounts[day]
eventID = parseInt eventID
started = dayEventCounts[day][eventID] if eventID is startEventID
finished = dayEventCounts[day][eventID] if eventID is finishEventID
completions.push
created: day
started: started
finished: finished
@levelCompletionsCache[cacheKey] = completions
@sendSuccess res, completions
module.exports = new AnalyticsPerDayHandler()

View file

@ -0,0 +1,9 @@
AnalyticsString = require './AnalyticsString'
Handler = require '../commons/Handler'
class AnalyticsStringHandler extends Handler
modelClass: AnalyticsString
jsonSchema: require '../../app/schemas/models/analytics_string'
hasAccess: (req) -> req.method in ['GET'] or req.user?.isAdmin()
module.exports = new AnalyticsStringHandler()

View file

@ -1,5 +1,7 @@
module.exports.handlers =
'analytics_log_event': 'analytics/analytics_log_event_handler'
'analytics_perday': 'analytics/analytics_perday_handler'
'analytics_string': 'analytics/analytics_string_handler'
# TODO: Disabling this until we know why our app servers CPU grows out of control.
# 'analytics_users_active': 'analytics/analytics_users_active_handler'
'article': 'articles/article_handler'

View file

@ -6,6 +6,7 @@ Feedback = require './feedbacks/LevelFeedback'
Handler = require '../commons/Handler'
mongoose = require 'mongoose'
async = require 'async'
utils = require '../lib/utils'
LevelHandler = class LevelHandler extends Handler
modelClass: Level
@ -375,9 +376,9 @@ LevelHandler = class LevelHandler extends Handler
# Build query
match = {$match: {$and: [{"state.complete": true}, {"playtime": {$gt: 0}}, {levelID: {$in: levelSlugs}}]}}
match["$match"]["$and"].push created: {$gte: new Date(startDay + "T00:00:00.000Z")} if startDay?
match["$match"]["$and"].push created: {$lt: new Date(endDay + "T00:00:00.000Z")} if endDay?
project = {"$project": {"_id": 0, "levelID": 1, "playtime": 1, "created": {"$concat": [{"$substr": ["$created", 0, 4]}, "-", {"$substr": ["$created", 5, 2]}, "-", {"$substr" : ["$created", 8, 2]}]}}}
match["$match"]["$and"].push _id: {$gte: utils.objectIdFromTimestamp(startDay + "T00:00:00.000Z")} if startDay?
match["$match"]["$and"].push _id: {$lt: utils.objectIdFromTimestamp(endDay + "T00:00:00.000Z")} if endDay?
project = {"$project": {"_id": 0, "levelID": 1, "playtime": 1, "created": {"$concat": [{"$substr": ["$created", 0, 10]}]}}}
group = {"$group": {"_id": {"created": "$created", "level": "$levelID"}, "average": {"$avg": "$playtime"}}}
query = Session.aggregate match, project, group

View file

@ -1,3 +1,39 @@
AnalyticsString = require '../analytics/AnalyticsString'
mongoose = require 'mongoose'
module.exports =
isID: (id) -> _.isString(id) and id.length is 24 and id.match(/[a-f0-9]/gi)?.length is 24
objectIdFromTimestamp: (timestamp) ->
# mongoDB ObjectId contains creation date in first 4 bytes
# So, it can be used instead of a redundant created field
# http://docs.mongodb.org/manual/reference/object-id/
# http://stackoverflow.com/questions/8749971/can-i-query-mongodb-objectid-by-date
# Convert string date to Date object (otherwise assume timestamp is a date)
timestamp = new Date(timestamp) if typeof(timestamp) == 'string'
# Convert date object to hex seconds since Unix epoch
hexSeconds = Math.floor(timestamp/1000).toString(16)
# Create an ObjectId with that hex timestamp
mongoose.Types.ObjectId(hexSeconds + "0000000000000000")
getAnalyticsStringID: (str, callback) ->
return callback -1 unless str?
@analyticsStringCache ?= {}
return callback @analyticsStringCache[str] if @analyticsStringCache[str]
insertString = =>
# http://docs.mongodb.org/manual/tutorial/create-an-auto-incrementing-field/#auto-increment-optimistic-loop
AnalyticsString.find({}, {_id: 1}).sort({_id: -1}).limit(1).exec (err, documents) =>
if err? then return callback -1
seq = if documents.length > 0 then documents[0]._id + 1 else 1
doc = new AnalyticsString _id: seq, v: str
doc.save (err) =>
if err? then return callback -1
@analyticsStringCache[str] = seq
callback seq
# Find existing string
AnalyticsString.findOne(v: str).exec (err, document) =>
if err? then return callback -1
if document
@analyticsStringCache[str] = document._id
return callback @analyticsStringCache[str]
insertString()

View file

@ -1,5 +1,6 @@
UserCodeProblem = require './UserCodeProblem'
Handler = require '../commons/Handler'
utils = require '../lib/utils'
class UserCodeProblemHandler extends Handler
modelClass: UserCodeProblem
@ -53,8 +54,8 @@ class UserCodeProblemHandler extends Handler
# Build query
match = if startDay? or endDay? then {$match: {$and: []}} else {$match: {}}
match["$match"]["$and"].push created: {$gte: new Date(startDay + "T00:00:00.000Z")} if startDay?
match["$match"]["$and"].push created: {$lt: new Date(endDay + "T00:00:00.000Z")} if endDay?
match["$match"]["$and"].push _id: {$gte: utils.objectIdFromTimestamp(startDay + "T00:00:00.000Z")} if startDay?
match["$match"]["$and"].push _id: {$lt: utils.objectIdFromTimestamp(endDay + "T00:00:00.000Z")} if endDay?
group = {"$group": {"_id": {"errMessage": "$errMessageNoLineInfo", "errHint": "$errHint", "language": "$language", "levelID": "$levelID"}, "count": {"$sum": 1}}}
sort = { $sort : { "_id.levelID": 1, count : -1, "_id.language": 1 } }
query = UserCodeProblem.aggregate match, group, sort