Merge branch 'master' into production

This commit is contained in:
Nick Winter 2015-01-01 15:00:03 -08:00
commit 5da7244f57
36 changed files with 1080 additions and 205 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -8,7 +8,7 @@ module.exports = [
focus:
bounds: [{x: 0, y: 0}, {x: 80, y: 68}]
target: "Hero Placeholder"
zoom: 2
zoom: 0.5
sound:
music:
file: "/music/music_level_2"

View file

@ -307,7 +307,7 @@ module.exports = nativeDescription: "čeština", englishDescription: "Czech", tr
tip_premature_optimization: "Předčasná optimalizace je původce všeho zla. - Donald Knuth"
tip_brute_force: "V případě pochybností, použijte brute force. - Ken Thompson"
tip_extrapolation: "Jsou jenom dva druhy lidí: ti, kteří mohou extrapolovat z nekompletních dat..."
# tip_superpower: "Coding is the closest thing we have to a superpower."
tip_superpower: "Kódování by se téměř dalo srovnávat se superschopnostmi."
game_menu:
inventory_tab: "Inventář"
@ -329,7 +329,7 @@ module.exports = nativeDescription: "čeština", englishDescription: "Czech", tr
inventory:
choose_inventory: "Nasadit předměty"
equipped_item: "Nasazeno"
# required_purchase_title: "Required"
required_purchase_title: "Vyžadováno"
available_item: "Dostupné"
restricted_title: "Omezeno"
should_equip: "(dvojklik pro nasazení)"
@ -482,13 +482,13 @@ module.exports = nativeDescription: "čeština", englishDescription: "Czech", tr
forum_prefix: "Pro ostatní veřejné věci, prosím zkuste "
forum_page: "naše fórum"
forum_suffix: "."
# faq_prefix: "There's also a"
# faq: "FAQ"
faq_prefix: "Také máme "
faq: "FAQ"
subscribe_prefix: "Pokud potřebujete pomoc s nějakou úrovní, prosím"
subscribe: "zakupte si CodeCombat předplatné"
subscribe_suffix: "a rádi vám pomůžeme s vaším kódem."
subscriber_support: "Již jste CodeCombat předplatitel, takže vaše emaily budou vyřízeny dříve."
# screenshot_included: "Screenshot included."
screenshot_included: "Snímek obrazovky zahrnut."
where_reply: "Kam máme odpovědět?"
send: "Odeslat připomínku"
contact_candidate: "Kontaktovat kandidáta" # Deprecated

View file

@ -3,8 +3,7 @@
slogan: "Learn to Code by Playing a Game"
no_ie: "CodeCombat does not run in Internet Explorer 8 or older. Sorry!" # Warning that only shows up in IE8 and older
no_mobile: "CodeCombat wasn't designed for mobile devices and may not work!" # Warning that shows up on mobile devices
play: "Play" # The big play button that just starts playing a level
try_it: "Try It" # Alternate wording for Play button
play: "Play" # The big play button that opens up the campaign view.
old_browser: "Uh oh, your browser is too old to run CodeCombat. Sorry!" # Warning that shows up on really old Firefox/Chrome/Safari
old_browser_suffix: "You can try anyway, but it probably won't work."
ipad_browser: "Bad news: CodeCombat doesn't run on iPad in the browser. Good news: our native iPad app is awaiting Apple approval."
@ -658,6 +657,7 @@
achievement_query_goals: "Key achievement off of level goals"
level_completion: "Level Completion"
pop_i18n: "Populate I18N"
tasks: "Tasks"
article:
edit_btn_preview: "Preview"

View file

@ -4,7 +4,6 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
no_ie: "¡Lo sentimos! CodeCombat no funciona en Internet Explorer 8 o versiones anteriores." # Warning that only shows up in IE8 and older
no_mobile: "¡CodeCombat no fue diseñado para dispositivos móviles y quizás no funcione!" # Warning that shows up on mobile devices
play: "Jugar" # The big play button that just starts playing a level
try_it: "Pruébalo" # Alternate wording for Play button
old_browser: "¡Oh! ¡Oh! Tu navegador es muy antiguo para correr CodeCombat. ¡Lo sentimos!" # Warning that shows up on really old Firefox/Chrome/Safari
old_browser_suffix: "Puedes probar de todas formas, pero probablemente no funcione."
ipad_browser: "Malas noticias: CodeCombat no funciona en el navegador de iPad. Buenas noticias: nuestra propia aplicación de iPad esta en espera para ser aprobada por Apple."
@ -593,63 +592,63 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
editor:
main_title: "Editor de CodeCombat"
article_title: "Editor de Artículo"
# thang_title: "Thang Editor"
thang_title: "Editor de Thangs"
level_title: "Editor de Nivel"
# achievement_title: "Achievement Editor"
achievement_title: "Editor de logros"
back: "Atrás"
revert: "Revertir"
revert_models: "Revertir Modelos"
pick_a_terrain: "Elije un Terreno"
small: "Pequeño"
grassy: "Herboso"
# fork_title: "Fork New Version"
# fork_creating: "Creating Fork..."
fork_title: "Fork de Nueva Versión"
fork_creating: "Creando Fork..."
generate_terrain: "Generar terreno"
more: "Más"
wiki: "Wiki"
live_chat: "Chat en vivo"
# thang_main: "Main"
# thang_spritesheets: "Spritesheets"
# thang_colors: "Colors"
# level_some_options: "Some Options?"
# level_tab_thangs: "Thangs"
# level_tab_scripts: "Scripts"
thang_main: "Principal"
thang_spritesheets: "Spritesheets"
thang_colors: "Colores"
level_some_options: "¿Algunas opciones?"
level_tab_thangs: "Thangs"
level_tab_scripts: "Scripts"
level_tab_settings: "Opciones"
level_tab_components: "Componentes"
level_tab_systems: "Sistemas"
level_tab_docs: "Documentación"
# level_tab_thangs_title: "Current Thangs"
level_tab_thangs_title: "Thangs Actuales"
level_tab_thangs_all: "Todo"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
level_tab_thangs_conditions: "Condiciones Iniciales"
level_tab_thangs_add: "Agregar Thangs"
delete: "Borrar"
duplicate: "Duplicar"
rotate: "Rotar"
level_settings_title: "Opciones"
level_component_tab_title: "Componentes Actuales"
level_component_btn_new: "Crear Nuevo Componente"
level_systems_tab_title: "Sistemas Actuales Systems"
level_systems_btn_new: "Crear Nuevo Sistema New System"
level_systems_tab_title: "Sistemas Actuales"
level_systems_btn_new: "Crear Nuevo Sistema"
level_systems_btn_add: "Agregar Sistema"
# level_components_title: "Back to All Thangs"
level_components_title: "Regresar a todos los Thangs"
level_components_type: "Tipo"
level_component_edit_title: "Editar Componente"
# level_component_config_schema: "Config Schema"
level_component_config_schema: "Config Schema"
level_component_settings: "Opciones"
level_system_edit_title: "Editar Sistema"
create_system_title: "Crear Nuevo Sistema"
new_component_title: "Crear Nuevo Componente"
new_component_field_system: "Sistema"
new_article_title: "Crear un Nuevo Artículo"
# new_thang_title: "Create a New Thang Type"
new_thang_title: "Crear un Nuevo tipo de Thang"
new_level_title: "Crear un Nuevo Nivel"
new_article_title_login: "Ingresa para Crear un Nuevo Artículo"
# new_thang_title_login: "Log In to Create a New Thang Type"
new_thang_title_login: "Ingresa para crear un nuevo tipo de Thang"
new_level_title_login: "Ingresa para Crear un Nuevo Nivel"
new_achievement_title: "Crear un Nuevo Logro"
new_achievement_title_login: "Ingresa para Crear un Nuevo Logro"
article_search_title: "Buscar Artículos aquí"
# thang_search_title: "Search Thang Types Here"
thang_search_title: "Buscar tipos de Thang aquí"
level_search_title: "Buscar Niveles aquí"
achievement_search_title: "Buscar logros"
read_only_warning2: "Nota: no puedes guardar ediciones aquí, porque no estas logeado."
@ -663,24 +662,24 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
edit_btn_preview: "Vista previa"
edit_article_title: "Editar Artículo"
# contribute:
# page_title: "Contributing"
# 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!"
# alert_account_message_intro: "Hey there!"
# alert_account_message: "To subscribe for class emails, you'll need to be logged in first."
# archmage_introduction: "One of the best parts about building games is they synthesize so many different things. Graphics, sound, real-time networking, social networking, and of course many of the more common aspects of programming, from low-level database management, and server administration to user facing design and interface building. There's a lot to do, and if you're an experienced programmer with a hankering to really dive into the nitty-gritty of CodeCombat, this class might be for you. We would love to have your help building the best programming game ever."
# class_attributes: "Class Attributes"
# archmage_attribute_1_pref: "Knowledge in "
# archmage_attribute_1_suf: ", or a desire to learn. Most of our code is in this language. If you're a fan of Ruby or Python, you'll feel right at home. It's JavaScript, but with a nicer syntax."
# archmage_attribute_2: "Some experience in programming and personal initiative. We'll help you get oriented, but we can't spend much time training you."
# how_to_join: "How To Join"
# join_desc_1: "Anyone can help out! Just check out our "
# join_desc_2: "to get started, and check the box below to mark yourself as a brave Archmage and get the latest news by email. Want to chat about what to do or how to get more deeply involved? "
# join_desc_3: ", or find us in our "
# join_desc_4: "and we'll go from there!"
# join_url_email: "Email us"
# join_url_hipchat: "public HipChat room"
# archmage_subscribe_desc: "Get emails on new coding opportunities and announcements."
contribute:
page_title: "Contribuyendo"
intro_blurb: "CodeCombat es 100% open source! Cientos de jugadores dedicados nos han ayudado a contruir el juego. Únete y escribe el siguiente capítulo de la misión de CodeCombat de enseñar al mundo a programar!"
alert_account_message_intro: "¡Hola!"
alert_account_message: "Para suscribirte para los correos, necesitas ingresar primero."
archmage_introduction: "Una de las mejores partes de hacer juegos es que sintetizan muchas cosas diferentes. Gráficas, sonido, redes, redes sociales y muchos aspectos comunes de programación, desde manejo de bases de datos y administración de servidores, hasta trabajar en el diseño y construcción de interfaces. Hay mucho para hacer, y si eres un programador con experiencia con el deseo de ingresar en el meollo del asunto de CodeCombat, esta clase puede ser para ti. Nos encantaría contar con tu ayuda para construir el mejor juego de programación."
class_attributes: "Atributos de Clase"
archmage_attribute_1_pref: "Conocimiento en "
archmage_attribute_1_suf: ", o un deseo de aprender. La mayor parte de nuestro código está en este lenguaje. Si eres un fan de Python o Ruby, te sentirás en casa. Es Javascript, pero con un mejor syntax."
archmage_attribute_2: "Alguna experiencia programando e iniciativa personal. Te ayudaremos a orientarte, pero no podemos perder mucho tiempo entrenando."
how_to_join: "Unirse:"
join_desc_1: "¡Cualquiera puede unirse! Sólo checa nuestro "
join_desc_2: "para comenzar, y pon un check abajo para marcarte como un valiente Archimago y conseguir las últimas noticias por email. ¿Quieres chatear sobre qué hacer o cómo involucrarte más? "
join_desc_3: ", o encuéntranos en "
join_desc_4: "y ahí empezaremos!"
join_url_email: "Escríbenos"
join_url_hipchat: "chat público HipChat"
archmage_subscribe_desc: "Obten correos de nuevas oportunidades y anuncios."
# artisan_introduction_pref: "We must construct additional levels! People be clamoring for more content, and we can only build so many ourselves. Right now your workstation is level one; our level editor is barely usable even by its creators, so be wary. If you have visions of campaigns spanning for-loops to"
# artisan_introduction_suf: ", then this class might be for you."
# artisan_attribute_1: "Any experience in building content like this would be nice, such as using Blizzard's level editors. But not required!"

View file

@ -1,6 +1,6 @@
module.exports = nativeDescription: "Македонски", englishDescription: "Macedonian", translation:
home:
slogan: "Научи да Програмираш Преку Игра"
slogan: "Научи да програмираш преку игра"
no_ie: "CodeCombat не работи во Internet Explorer верзија 8 или постара. Извини!" # Warning that only shows up in IE8 and older
no_mobile: "CodeCombat не е дизајнирана за мобилни уреди и може да не работи!" # Warning that shows up on mobile devices
play: "Играј" # The big play button that just starts playing a level
@ -9,8 +9,8 @@ module.exports = nativeDescription: "Македонски", englishDescription:
old_browser_suffix: "Можеш да пробаш и покрај тоа, но најверојатно нема да работи."
ipad_browser: "Лоши вести: CodeCombat не работи во прелистувачот на iPad. Добри вести: Нашата апликација за iPad е готова и чека одобрение од Apple."
campaign: "Кампања"
for_beginners: "За Почетници"
multiplayer: "Повеќе Играчи" # Not currently shown on home page
for_beginners: "За почетници"
multiplayer: "Повеќе играчи" # Not currently shown on home page
for_developers: "За Developer-и" # Not currently shown on home page.
or_ipad: "Или симни за iPad"
@ -24,7 +24,7 @@ module.exports = nativeDescription: "Македонски", englishDescription:
profile: "Профил"
stats: "Статистики"
# code: "Code"
# admin: "Admin" # Only shows up when you are an admin
admin: "Админ" # Only shows up when you are an admin
home: "Дома"
contribute: "Допринеси"
legal: "Законски"
@ -43,87 +43,87 @@ module.exports = nativeDescription: "Македонски", englishDescription:
diplomat_suggestion:
title: "Помогни да се преведе CodeCombat!" # This shows up when a player switches to a non-English language using the language selector.
sub_heading: "Ни требаат твоите јазични вештини."
pitch_body: "Ние ја развиваме CodeCombat на Англиски, но веќе имаме играчи низ целиот свет. Многу од нив сакаат да играат на Македонски, а не разбираат Англиски, па ако ги зборуваш и двата јазика, размисли дали би сакал/а да се зачлениш како Дипломат и да помогнеш да се преведат на Македонски CodeCombat веб сајтот и сите нивоа од играта."
missing_translations: "Додека не преведеме сè на Македонски, содржината ќе биде на Англиски каде што Македонскиот не е достапен."
pitch_body: "Ние ја развиваме CodeCombat на англиски, но веќе имаме играчи низ целиот свет. Многу од нив сакаат да играат на македонски, а не разбираат англиски, па ако ги зборуваш и двата јазика, размисли дали би сакал/а да се зачлениш како Дипломат и да помогнеш да се преведат на македонски CodeCombat веб сајтот и сите нивоа од играта."
missing_translations: "Додека не преведеме сè на македонски, содржината ќе биде на англиски каде што македонскиот не е достапен."
learn_more: "Научи повеќе за тоа како е да се биде Дипломат"
subscribe_as_diplomat: "Зачлени се како Дипломат"
# play:
# play_as: "Play As" # Ladder page
# spectate: "Spectate" # Ladder page
# players: "players" # Hover over a level on /play
# hours_played: "hours played" # Hover over a level on /play
# items: "Items" # 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"
# skills_granted: "Skills Granted" # Property documentation details
# heroes: "Heroes" # Tooltip on hero shop button from /play
# achievements: "Achievements" # Tooltip on achievement list button from /play
# account: "Account" # Tooltip on account button from /play
# settings: "Settings" # Tooltip on settings button from /play
# next: "Next" # Go from choose hero to choose inventory before playing a level
# change_hero: "Change Hero" # Go back from choose inventory to choose hero
# choose_inventory: "Equip Items"
# buy_gems: "Buy Gems"
# campaign_desert: "Desert Campaign"
# campaign_forest: "Forest Campaign"
# campaign_dungeon: "Dungeon Campaign"
# subscription_required: "Subscription Required"
# free: "Free"
# subscribed: "Subscribed"
# older_campaigns: "Older Campaigns"
# anonymous: "Anonymous Player"
# level_difficulty: "Difficulty: "
# campaign_beginner: "Beginner Campaign"
# awaiting_levels_adventurer_prefix: "We release five levels per week."
# awaiting_levels_adventurer: "Sign up as an Adventurer"
# awaiting_levels_adventurer_suffix: "to be the first to play new levels."
# choose_your_level: "Choose Your Level" # The rest of this section is the old play view at /play-old and isn't very important.
# adventurer_prefix: "You can jump to any level below, or discuss the levels on "
# adventurer_forum: "the Adventurer forum"
# adventurer_suffix: "."
# campaign_old_beginner: "Old Beginner Campaign"
# campaign_old_beginner_description: "... in which you learn the wizardry of programming."
# campaign_dev: "Random Harder Levels"
# campaign_dev_description: "... in which you learn the interface while doing something a little harder."
# campaign_multiplayer: "Multiplayer Arenas"
# campaign_multiplayer_description: "... in which you code head-to-head against other players."
# campaign_player_created: "Player-Created"
# campaign_player_created_description: "... in which you battle against the creativity of your fellow <a href=\"/contribute/artisan\">Artisan Wizards</a>."
# campaign_classic_algorithms: "Classic Algorithms"
# campaign_classic_algorithms_description: "... in which you learn the most popular algorithms in Computer Science."
play:
play_as: "Играј како" # Ladder page
spectate: "Набљудувај" # Ladder page
players: "играчи" # Hover over a level on /play
hours_played: "часови изиграни" # Hover over a level on /play
items: "Предмети" # Tooltip on item shop button from /play
unlock: "Отклучи" # For purchasing items and heroes
confirm: "Потврди"
owned: "Во сопственост" # For items you own
locked: "Заклучено"
purchasable: "Може да се купи" # For a hero you unlocked but haven't purchased
available: "Достапно"
skills_granted: "Доделени вештини" # Property documentation details
heroes: "Херои" # Tooltip on hero shop button from /play
achievements: "Постигнувања" # Tooltip on achievement list button from /play
account: "Сметка" # Tooltip on account button from /play
settings: "Поставки" # Tooltip on settings button from /play
next: "Следно" # Go from choose hero to choose inventory before playing a level
change_hero: "Смени херој" # Go back from choose inventory to choose hero
choose_inventory: "Опреми се"
buy_gems: "Купи скапоцени камења"
campaign_desert: "Пустинска кампања"
campaign_forest: "Шумска кампања"
campaign_dungeon: "Занданска кампања"
subscription_required: "Потребно е зачленување"
free: "Бесплатно"
subscribed: "Зачленет"
older_campaigns: "Постари кампањи"
anonymous: "Анонимен играч"
level_difficulty: "Тешкотија: "
campaign_beginner: "Почетничка кампања"
awaiting_levels_adventurer_prefix: "Пуштаме пет нивоа неделно."
awaiting_levels_adventurer: "Зачлени се како Авантурист"
awaiting_levels_adventurer_suffix: "за да бидеш првиот кој ќе ги игра новите нивоа."
choose_your_level: "Избери го твоето ниво" # The rest of this section is the old play view at /play-old and isn't very important.
adventurer_prefix: "Можеш да отидеш на било кое од подолните нивоа, или да дискутираш за нивоата на "
adventurer_forum: "форумот на Авантуристите"
adventurer_suffix: "."
campaign_old_beginner: "Стара почетничка кампања"
campaign_old_beginner_description: "... во која учиш за волшепството на програмирањето."
campaign_dev: "Призволни потешки нивоа"
campaign_dev_description: "... во кои го учиш интерфејсот додека правиш нешто малку потешко."
campaign_multiplayer: "Арени за повеќе играчи"
campaign_multiplayer_description: "... во кои кодираш лице-во-лице против други играчи."
campaign_player_created: "Направено од играчи"
campaign_player_created_description: "... се бориш наспроти креативноста на останатите играчи од <a href=\"/contribute/artisan\">Волшебничкиот занает</a>."
campaign_classic_algorithms: "Класични алгоритми"
campaign_classic_algorithms_description: "... во кои ги учиш најпопуларните алгоритми во компјутерската наука."
login:
sign_up: "Направи Сметка"
sign_up: "Направи сметка"
log_in: "Најави се"
logging_in: "Најавувањето е во тек"
log_out: "Одјави се"
forgot_password: "Ја заборави својата лозинка?"
authenticate_gplus: "Провери G+ најава"
load_profile: "Вчитај G+ Профил"
load_email: "Вчитај G+ Email"
load_profile: "Вчитај G+ профил"
load_email: "Вчитај G+ e-mail"
finishing: "Завршување"
sign_in_with_facebook: "Најави се со Facebook"
sign_in_with_gplus: "Најави се со G+"
signup_switch: "Сакаш да направиш сметка?"
signup:
email_announcements: "Примај соопштенија преку email"
email_announcements: "Примај соопштенија преку e-mail"
creating: "Сметката се прави..."
sign_up: "Направи Сметка"
sign_up: "Направи сметка"
log_in: "најави се со лозинка"
social_signup: "Или, можеш да се пријавиш преку Facebook или G+:"
required: "Мораш да се најавиш за да имаш пристап таму."
login_switch: "Веќе имаш сметка?"
recover:
recover_account_title: "Врати Сметка"
recover_account_title: "Врати сметка"
send_password: "Испрати лозинка за враќање"
recovery_sent: "Email-от за враќање на лозинката е испратен."
recovery_sent: "E-mail-от за враќање на лозинката е испратен."
items:
primary: "Главно"
@ -151,37 +151,37 @@ module.exports = nativeDescription: "Македонски", englishDescription:
help: "Помош"
# watch: "Watch"
# unwatch: "Unwatch"
submit_patch: "Поднеси Закрпа"
submit_changes: "Поднеси Промени"
submit_patch: "Поднеси закрпа"
submit_changes: "Поднеси промени"
# general:
# and: "and"
# name: "Name"
# date: "Date"
# body: "Body"
# version: "Version"
# submitter: "Submitter"
# submitted: "Submitted"
general:
and: "и"
name: "Име"
date: "Датум"
body: "Тело" # Original was 'Body'. Not sure if this is the best translation.
version: "Верзија"
submitter: "Подносител"
submitted: "Поднесено"
# commit_msg: "Commit Message"
# review: "Review"
# version_history: "Version History"
# version_history_for: "Version History for: "
version_history: "Историја на верзии"
version_history_for: "Историја на верзии за: "
# select_changes: "Select two changes below to see the difference."
# undo: "Undo (Ctrl+Z)"
# redo: "Redo (Ctrl+Shift+Z)"
# play_preview: "Play preview of current level"
# result: "Result"
# results: "Results"
# description: "Description"
# or: "or"
# subject: "Subject"
# email: "Email"
# password: "Password"
# message: "Message"
result: "Резултат"
results: "Резултати"
description: "Опис"
or: "или"
subject: "Предмет на пораката" # Original: 'Subject'. Translated as 'Message subject' because in macedonian the word 'Предмет' can have different meaning.
email: "E-mail"
password: "Лозинка"
message: "Порака"
# code: "Code"
# ladder: "Ladder"
# when: "When"
# opponent: "Opponent"
when: "Кога"
opponent: "Противник"
# rank: "Rank"
# score: "Score"
# win: "Win"
@ -190,7 +190,7 @@ module.exports = nativeDescription: "Македонски", englishDescription:
# easy: "Easy"
# medium: "Medium"
# hard: "Hard"
# player: "Player"
player: "Играч"
# player_level: "Level" # Like player level 5, not like level: Dungeons of Kithgard
units:
@ -209,45 +209,45 @@ module.exports = nativeDescription: "Македонски", englishDescription:
year: "година"
years: "години"
# play_level:
# done: "Done"
# home: "Home" # Not used any more, will be removed soon.
# level: "Level" # Like "Level: Dungeons of Kithgard"
# skip: "Skip"
# game_menu: "Game Menu"
# guide: "Guide"
play_level:
done: "Готово"
home: "Дома" # Not used any more, will be removed soon.
level: "Ниво" # Like "Level: Dungeons of Kithgard"
skip: "Прескокни"
game_menu: "Мени"
guide: "Водич"
# restart: "Restart"
# goals: "Goals"
# goal: "Goal"
goals: "Цели"
goal: "Цел"
# running: "Running..."
# success: "Success!"
# incomplete: "Incomplete"
# timed_out: "Ran out of time"
success: "Успешно!"
incomplete: "Некомплетирано"
timed_out: "Истече времето"
# failing: "Failing"
# action_timeline: "Action Timeline"
# click_to_select: "Click on a unit to select it."
# control_bar_multiplayer: "Multiplayer"
# control_bar_join_game: "Join Game"
control_bar_multiplayer: "Повеќе играчи"
control_bar_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: "Победа"
# victory_title_prefix: ""
# victory_title_suffix: " Complete"
# victory_sign_up: "Sign Up to Save Progress"
# victory_sign_up_poke: "Want to save your code? Create a free account!"
# victory_rate_the_level: "Rate the level: " # Only in old-style levels.
victory_sign_up: "Направи сметка за да го зачуваш напредокот"
victory_sign_up_poke: "Сакаш да го зачуваш твојот код? Направи бесплатна сметка!"
victory_rate_the_level: "Оцени го нивото: " # Only in old-style levels.
# victory_return_to_ladder: "Return to Ladder"
# victory_play_continue: "Continue"
# victory_saving_progress: "Saving Progress"
# victory_go_home: "Go Home" # Only in old-style levels.
# victory_review: "Tell us more!" # Only in old-style levels.
# victory_hour_of_code_done: "Are You Done?"
victory_play_continue: "Продолжи"
victory_saving_progress: "Напредокот се зачувува"
victory_go_home: "Оди дома" # Only in old-style levels.
victory_review: "Кажи ни повеќе!" # Only in old-style levels.
victory_hour_of_code_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"
# guide_title: "Guide"
victory_experience_gained: "Добиено искуство"
victory_gems_gained: "Добиени скапоцени камења"
guide_title: "Водич"
# tome_minion_spells: "Your Minions' Spells" # Only in old-style levels.
# tome_read_only_spells: "Read-Only Spells" # Only in old-style levels.
# tome_other_units: "Other Units" # Only in old-style levels.
@ -326,16 +326,16 @@ module.exports = nativeDescription: "Македонски", englishDescription:
# multiplayer_caption: "Play with friends!"
# auth_caption: "Save your progress."
# inventory:
inventory:
# choose_inventory: "Equip Items"
# equipped_item: "Equipped"
# required_purchase_title: "Required"
# available_item: "Available"
# restricted_title: "Restricted"
required_purchase_title: "Задолжително"
available_item: "Достапно"
restricted_title: "Забрането"
# should_equip: "(double-click to equip)"
# equipped: "(equipped)"
# locked: "(locked)"
# restricted: "(restricted in this level)"
locked: "(заклучено)"
restricted: "(забрането во ова ниво)"
# equip: "Equip"
# unequip: "Unequip"

View file

@ -14,6 +14,8 @@ module.exports = class LevelComponent extends CocoModel
@PlansID: '524b7b517fc0f6d51900000d'
@ProgrammableID: '524b7b5a7fc0f6d51900000e'
@MovesID: '524b7b8c7fc0f6d519000013'
@MissileID: '524cc2593ea855e0ab000142'
@FindsPaths: '52872b0ead92b98561000002'
urlRoot: '/db/level.component'
set: (key, val, options) ->

View file

@ -16,7 +16,7 @@ defaultTasks = [
'Choose music file in Introduction script.'
'Add to a campaign.'
'Publish for playtesting.'
'Publish.'
'Choose level options like required/restricted gear.'
'Create achievements, including unlocking next level.'
@ -25,17 +25,17 @@ defaultTasks = [
'Playtest with a couple random seeds.'
'Make sure the level ends promptly on success and failure.'
'Remove/simplify unnecessary doodad collision.'
'Release to adventurers.'
'Release to adventurers via MailChimp.'
'Write the description.'
'Translate the sample code comments.'
'Add Io/Clojure/Lua/CoffeeScript.'
'Write the guide.'
'Write a loading tip, if needed.'
'Populate i18n.'
'Click the Populate i18n button.'
'Mark whether it requires a subscription (after adventurer week).'
'Release to everyone.'
'Release to everyone via MailChimp.'
'Check completion/engagement/problem analytics.'
'Do any custom scripting, if needed.'

View file

@ -168,6 +168,8 @@ _.extend ThangTypeSchema.properties,
i18n: {type: 'object', format: 'i18n', props: ['name', 'description', 'extendedName', 'unlockLevelName'], description: 'Help translate this ThangType\'s name and description.'}
extendedName: {type: 'string', title: 'Extended Hero Name', description: 'The long form of the hero\'s name. Ex.: "Captain Anya Weston".'}
unlockLevelName: {type: 'string', title: 'Unlock Level Name', description: 'The name of the level in which the hero is unlocked.'}
tasks: c.array {title: 'Tasks', description: 'Tasks to be completed for this ThangType.'}, c.task
ThangTypeSchema.required = []

View file

@ -21,3 +21,12 @@
bottom: 0
right: 0
width: 75%
#analytics-button
position: absolute
right: 1%
top: 1%
padding: 3px 8px
#analytics-modal .modal-content
background-color: white

View file

@ -11,7 +11,7 @@ block modal-body-content
p(data-i18n="diplomat_suggestion.missing_translations") Until we can translate everything into {English}, you'll see English when {English} isn't available.
p
a(href="/contribute#diplomat", data-i18n="diplomat_suggestion.learn_more") Learn more about being a Diplomat
a(href="/contribute/diplomat", data-i18n="diplomat_suggestion.learn_more") Learn more about being a Diplomat
block modal-footer-content
button.btn.btn-primary.btn-large#subscribe-button(data-i18n="diplomat_suggestion.subscribe_as_diplomat") Subscribe as a Diplomat

View file

@ -40,5 +40,40 @@ block outer_content
#right-column
#campaign-view
#campaign-level-view.hidden
if campaignDropOffs
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
.modal-content
.modal-header
button.close(type="button", data-dismiss="modal", aria-label="Close")
span(aria-hidden="true") &times;
h4.modal-title Analytics
.modal-body
if campaignDropOffs.startDay
if campaignDropOffs.endDay
div #{campaignDropOffs.startDay} to #{campaignDropOffs.endDay}
else
div #{campaignDropOffs.startDay} to today
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 %
tbody
- for (var i = 0; i < campaignDropOffs.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
block footer

View file

@ -5,8 +5,42 @@
a(href="/editor/level/#{level.get('slug')}", target="_blank") (edit)
p= level.get('description')
h2 TODO: actually put useful stuff in here
h4 Completion Rates
if levelCompletions
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
thead
tr
td Date
td Started
td Finished
td Completion %
tbody
- for (var i = 0; i < levelCompletions.length; i++)
tr
td= levelCompletions[i].created
td= levelCompletions[i].started
td= levelCompletions[i].finished
td= levelCompletions[i].rate
else
div Loading...
h4 Average Playtimes
if levelPlaytimes
table.table.table-bordered.table-condensed.table-hover(style='font-size:10pt')
thead
tr
td Date
td Average (s)
tbody
- for (var i = 0; i < levelPlaytimes.length; i++)
tr
td= levelPlaytimes[i].created
td= levelPlaytimes[i].average.toFixed(2)
else
div Loading...
if level.get('tasks')
.tasks
h3 Tasks (read only)

View file

@ -14,6 +14,7 @@ block tableHeader
th(data-i18n="general.name") Name
th(data-i18n="general.description") Description
th(data-i18n="general.version") Version
th(data-i18n="editor.tasks") Tasks
block tableBody
for thang in documents
@ -25,4 +26,10 @@ block tableBody
| #{thang.get('name')}
td.body-row #{thang.get('description')}
- var version = thang.get('version')
td #{version.major}.#{version.minor}
td #{version.major}.#{version.minor}
- var tasks = thang.get('tasks');
if tasks && tasks.length
- var completed = tasks.filter(function(t) { return t.complete; });
td #{completed.length}/#{tasks.length}
else
td

View file

@ -24,4 +24,4 @@ block content
span.spl.spr - #{playCount.sessions}
span(data-i18n="play.players") players
.play-text-container
.overlay-text.play-text= playText
.overlay-text.play-text(data-i18n="home.play") Play

View file

@ -32,9 +32,6 @@ module.exports = class HomeView extends RootView
c.explainsHourOfCode = @explainsHourOfCode
c.isMobile = @isMobile()
c.isIPadBrowser = @isIPadBrowser()
c.playText = $.i18n.t('home.try_it', false)
if c.playText is 'home.try_it'
c.playText = $.i18n.t 'home.play' # Temporary fallback for not having many try_it translations yet.
c
onClickBeginnerCampaign: (e) ->

View file

@ -6,7 +6,7 @@ app = require 'core/application'
class SearchCollection extends Backbone.Collection
initialize: (modelURL, @model, @term, @projection) ->
@url = "#{modelURL}?project="
if @projection? and not (@projection == [])
if @projection?.length
@url += 'created,permissions'
@url += ',' + projected for projected in projection
else @url += 'true'

View file

@ -47,6 +47,8 @@ module.exports = class CampaignEditorView extends RootView
@listenToOnce @levels, 'sync', @onFundamentalLoaded
@listenToOnce @achievements, 'sync', @onFundamentalLoaded
_.delay @getCampaignDropOffs, 1000
loadThangTypeNames: ->
# Load the names of the ThangTypes that this level's Treema nodes might want to display.
originals = []
@ -130,6 +132,7 @@ module.exports = class CampaignEditorView extends RootView
getRenderData: ->
c = super()
c.campaign = @campaign
c.campaignDropOffs = @campaignDropOffs
c
onClickSaveButton: ->
@ -236,6 +239,35 @@ module.exports = class CampaignEditorView extends RootView
achievement.set 'rewards', newRewards
if achievement.hasLocalChanges()
@toSave.add achievement
getCampaignDropOffs: =>
# Fetch last 7 days of campaign drop-off rates
startDay = new Date()
startDay.setDate(startDay.getUTCDate() - 6)
startDay = startDay.getUTCFullYear() + '-' + (startDay.getUTCMonth() + 1) + '-' + startDay.getUTCDate()
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
@campaignDropOffs.levels = _.map @campaignDropOffs.levels, mapFn, @
@campaignDropOffs.startDay = startDay
@render()
# TODO: Why do we need this url dash?
request = @supermodel.addRequestResource 'campaign_drop_offs', {
url: '/db/analytics_log_event/-/campaign_drop_offs'
data: {startDay: startDay, slugs: [@campaignHandle]}
method: 'POST'
success: success
}, 0
request.load()
class LevelsNode extends TreemaObjectNode
valueClass: 'treema-levels'

View file

@ -14,11 +14,61 @@ module.exports = class CampaignLevelView extends CocoView
@fullLevel.fetch()
@listenToOnce @fullLevel, 'sync', => @render?()
@levelSlug = @level.get('slug')
@getLevelCompletions()
@getLevelPlaytimes()
getRenderData: ->
c = super()
c.level = if @fullLevel.loaded then @fullLevel else @level
c.levelCompletions = @levelCompletions
c.levelPlaytimes = @levelPlaytimes
c
onClickClose: ->
@$el.addClass('hidden')
@trigger 'hidden'
getLevelCompletions: ->
# Fetch last 7 days of level completion counts
success = (data) =>
return if @destroyed
data.sort (a, b) -> if a.created < b.created then 1 else -1
mapFn = (item) ->
item.rate = (item.finished / item.started * 100).toFixed(2)
item
@levelCompletions = _.map data, mapFn, @
@render()
startDay = new Date()
startDay.setDate(startDay.getUTCDate() - 6)
startDay = startDay.getUTCFullYear() + '-' + (startDay.getUTCMonth() + 1) + '-' + startDay.getUTCDate()
# TODO: Why do we need this url dash?
request = @supermodel.addRequestResource 'level_completions', {
url: '/db/analytics_log_event/-/level_completions'
data: {startDay: startDay, slug: @levelSlug}
method: 'POST'
success: success
}, 0
request.load()
getLevelPlaytimes: ->
# Fetch last 7 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 = new Date()
startDay.setDate(startDay.getUTCDate() - 6)
startDay = startDay.getUTCFullYear() + '-' + (startDay.getUTCMonth() + 1) + '-' + startDay.getUTCDate()
# TODO: Why do we need this url dash?
request = @supermodel.addRequestResource 'playtime_averages', {
url: '/db/level/-/playtime_averages'
data: {startDay: startDay, slugs: [@levelSlug]}
method: 'POST'
success: success
}, 0
request.load()

View file

@ -15,8 +15,8 @@ CocoCollection = require 'collections/CocoCollection'
LC = (componentName, config) -> original: LevelComponent[componentName + 'ID'], majorVersion: 0, config: config
DEFAULT_COMPONENTS =
Unit: [LC('Equips')]
Hero: [LC('Equips')]
Unit: [LC('Equips'), LC('FindsPaths')]
Hero: [LC('Equips'), LC('FindsPaths')]
Floor: [
LC('Exists', stateless: true)
LC('Physical', width: 20, height: 17, depth: 2, shape: 'sheet', pos: {x: 10, y: 8.5, z: 1})
@ -35,6 +35,7 @@ DEFAULT_COMPONENTS =
Misc: [LC('Exists'), LC('Physical')]
Mark: []
Item: [LC('Item')]
Missile: [LC('Missile')]
module.exports = class ThangComponentsEditView extends CocoView
id: 'thang-components-edit-view'

View file

@ -23,6 +23,106 @@ storage = require 'core/storage'
CENTER = {x: 200, y: 300}
commonTasks = [
'Upload the art.'
'Set up the vector icon.'
]
displayedThangTypeTasks = [
'Configure the idle action.'
'Configure the positions (registration point, etc.).'
'Set shadow diameter to 0 if needed.'
'Set scale to 0.3, 0.5, or whatever is appropriate.'
'Set rotation to isometric if needed.'
'Set accurate Physical size, shape, and default z.'
'Set accurate Collides collision information if needed.'
'Double-check that fixedRotation is accurate, if it collides.'
]
animatedThangTypeTasks = displayedThangTypeTasks.concat [
'Configure the non-idle actions.'
'Configure any per-action registration points needed.'
'Add flipX per action if needed to face to the right.'
'Make sure any death and attack actions do not loop.'
'Add defaultSimlish if needed.'
'Add selection sounds if needed.'
'Add per-action sound triggers.'
'Add team color groups.'
]
containerTasks = displayedThangTypeTasks.concat [
'Select viable terrains if not universal.'
'Set Exists stateless: true if needed.'
]
purchasableTasks = [
'Add a tier, or 10 + desired tier if not ready yet.'
'Add a gem cost.'
'Write a description.'
'Click the Populate i18n button.'
]
defaultTasks =
Unit: commonTasks.concat animatedThangTypeTasks.concat [
'Start a new name category in names.coffee if needed.'
'Set to Allied to correct team (ogres, humans, or neutral).'
'Add AutoTargetsNearest or FightsBack if needed.'
'Add other Components like Shoots or Casts if needed.'
'Configure other Components, like Moves, Attackable, Attacks, etc.'
'Override the HasAPI type if it will not be correctly inferred.'
]
Hero: commonTasks.concat animatedThangTypeTasks.concat purchasableTasks.concat [
'Set the hero class.'
'Add Extended Hero Name.'
'Upload Hero Doll Images.'
'Start a new name category in names.coffee.'
'Set up hero stats in Equips, Attackable, Moves.'
'Set Collects collectRange to 2, Sees visualRange to 60.'
'Add any custom hero abilities.'
'Add to ThangType model hard-coded hero ids/classes list.'
'Add to LevelHUDView hard-coded hero short names list.'
'Add to InventoryView hard-coded hero gender list.'
'Add to PlayHeroesModal hard-coded hero positioning logic.'
'Add as unlock to a level and add unlockLevelName here.'
]
Floor: commonTasks.concat containerTasks.concat [
'Add 10 x 8.5 snapping.'
'Set fixed rotation.'
'Make sure everything is scaled to tile perfectly.'
'Adjust SingularSprite floor scale list if necessary.'
]
Wall: commonTasks.concat containerTasks.concat [
'Add 4x4 snapping.'
'Set fixed rotation.'
'Set up and tune complicated wall-face actions.'
'Make sure everything is scaled to tile perfectly.'
]
Doodad: commonTasks.concat containerTasks.concat [
'Add to GenerateTerrainModal logic if needed.'
]
Misc: commonTasks.concat [
'Add any misc tasks for this misc ThangType.'
]
Mark: commonTasks.concat [
'Check the animation framerate.'
'Double-check that bottom of mark is just touching registration point.'
]
Item: commonTasks.concat purchasableTasks.concat [
'Set the hero class if class-specific.'
'Upload Paper Doll Images.'
]
Missile: commonTasks.concat animatedThangTypeTasks.concat [
'Make sure there is a launch sound trigger.'
'Make sure there is a hit sound trigger.'
'Make sure there is a die animation.'
'Add Arrow, Shell, Beam, or other missile Component.'
'Choose Missile.leadsShots and Missile.shootsAtGround.'
'Choose Moves.maxSpeed and other config.'
'Choose Expires.lifespan config if needed.'
'Set spriteType: singular if needed for proper rendering.'
'Add HasAPI if the missile should show up in findEnemyMissiles.'
]
module.exports = class ThangTypeEditView extends RootView
id: 'thang-type-edit-view'
className: 'editor'
@ -435,6 +535,8 @@ module.exports = class ThangTypeEditView extends RootView
Backbone.Mediator.publish 'editor:thang-type-kind-changed', kind: kind
if kind in ['Doodad', 'Floor', 'Wall'] and not @treema.data.terrains
@treema.set '/terrains', ['Grass', 'Dungeon', 'Indoor', 'Desert'] # So editors know to set them.
if not @treema.data.tasks
@treema.set '/tasks', (name: t for t in defaultTasks[kind])
onSelectNode: (e, selected) =>
selected = selected[0]

View file

@ -6,7 +6,7 @@ module.exports = class ThangTypeSearchView extends SearchView
model: require 'models/ThangType'
modelURL: '/db/thang.type'
tableTemplate: require 'templates/editor/thang/table'
projection: ['original', 'name', 'version', 'description', 'slug', 'kind', 'rasterIcon']
projection: ['original', 'name', 'version', 'description', 'slug', 'kind', 'rasterIcon', 'tasks']
page: 'thang'
getRenderData: ->

View file

@ -71,7 +71,7 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
current_directory = os.path.dirname(os.path.realpath(sys.argv[0]))
allowedMongoVersions = ["v2.6.0","v2.6.1","v2.6.4","v2.6.5"]
allowedMongoVersions = ["v2.6"]
if which("mongod") and any(i in subprocess.check_output("mongod --version",shell=True) for i in allowedMongoVersions):
mongo_executable = "mongod"
else:

View file

@ -0,0 +1,244 @@
// Print out campaign drop-off rates
// Drop off: last started or finished level event
// Adjust startDate below for different timeframe than last 7 days.
// Usage:
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
// Ignores the order at which levels are completed
// Ignores level skipping
// TODO: Is our overall drop-off rate correct?
// TODO: What's the right time frame for this data?
// TODO: Calculate completion rates per-level, and campaign overall
var today = new Date();
today = today.toISOString().substr(0, 10);
print("Today is " + today);
var todayMinus6 = new Date();
todayMinus6.setDate(todayMinus6.getUTCDate() - 6);
var startDate = todayMinus6.toISOString().substr(0, 10) + "T00:00:00.000Z";
// startDate = "2014-12-01T00:00:00.000Z";
print("Start date is " + startDate)
var cursor = db['analytics.log.events'].find({
$and: [
{"created": { $gte: ISODate(startDate)}},
{$or: [ {"event" : 'Started Level'}, {"event" : 'Saw Victory'}]}
]
});
var longestLevelName = -1;
// Copied from WorldMapView
var dungeonLevels = [
'dungeons-of-kithgard',
'gems-in-the-deep',
'shadow-guard',
'kounter-kithwise',
'crawlways-of-kithgard',
'forgetful-gemsmith',
'true-names',
'favorable-odds',
'the-raised-sword',
'haunted-kithmaze',
'riddling-kithmaze',
'descending-further',
'the-second-kithmaze',
'dread-door',
'known-enemy',
'master-of-names',
'lowly-kithmen',
'closing-the-distance',
'tactical-strike',
'the-final-kithmaze',
'the-gauntlet',
'kithgard-gates',
'cavern-survival'
];
var forestLevels = [
'defense-of-plainswood',
'winding-trail',
'patrol-buster',
'endangered-burl',
'village-guard',
'thornbush-farm',
'back-to-back',
'ogre-encampment',
'woodland-cleaver',
'shield-rush',
'peasant-protection',
'munchkin-swarm',
'munchkin-harvest',
'swift-dagger',
'shrapnel',
'arcane-ally',
'touch-of-death',
'bonemender',
'coinucopia',
'copper-meadows',
'drop-the-flag',
'deadly-pursuit',
'rich-forager',
'siege-of-stonehold',
'multiplayer-treasure-grove',
'dueling-grounds'
];
var desertLevels = [
'the-dunes',
'the-mighty-sand-yak',
'oasis',
'sarven-road',
'sarven-gaps',
'thunderhooves',
'medical-attention',
'minesweeper',
'sarven-sentry',
'keeping-time',
'hoarding-gold',
'decoy-drill',
'yakstraction',
'sarven-brawl'
];
var campaigns = {
'dungeon': dungeonLevels,
'forest': forestLevels,
'desert': desertLevels
};
// Bucketize events by user
print("Getting event data...");
var userProgression = {};
while (cursor.hasNext()) {
var doc = cursor.next();
var created = doc.created;
var event = doc.event;
if (event === 'Saw Victory') var level = doc.properties.level.toLowerCase().replace(/ /g, '-');
else var level = doc.properties.levelID
if (level) {
if (level.length > longestLevelName) longestLevelName = level.length;
var user = doc.user.valueOf();
if (!userProgression[user]) userProgression[user] = [];
userProgression[user].push({
created: created,
event: event,
level: level
});
}
}
longestLevelName += 2;
print("Processing data...");
// Order user progression by created
for (user in userProgression) userProgression[user].sort(function (a,b) {return a.created < b.created ? -1 : 1});
// Per-level start/drop/finish/drop
var levelProgression = {};
for (user in userProgression) {
for (var i = 0; i < userProgression[user].length; i++) {
var event = userProgression[user][i].event;
var level = userProgression[user][i].level;
if (!levelProgression[level]) {
levelProgression[level] = {
started: 0,
startDropped: 0,
finished: 0,
finishDropped: 0
};
}
if (event === 'Started Level') {
levelProgression[level].started++;
if (i === userProgression[user].length - 1) levelProgression[level].startDropped++;
}
else if (event === 'Saw Victory') {
levelProgression[level].finished++;
if (i === userProgression[user].length - 1) levelProgression[level].finishDropped++;
}
}
}
// Put in campaign order
// Calculate overall campaign stats
var campaignRates = {};
for (level in levelProgression) {
for (campaign in campaigns) {
if (campaigns[campaign].indexOf(level) >= 0) {
var started = levelProgression[level].started;
var startDropped = levelProgression[level].startDropped;
var finished = levelProgression[level].finished;
var finishDropped = levelProgression[level].finishDropped;
if (!campaignRates[campaign]) {
campaignRates[campaign] = { levels: [], overall: {
started: 0,
startDropped: 0,
finished: 0,
finishDropped: 0
}};
}
campaignRates[campaign].levels.push({
level: level,
started: started,
startDropped: startDropped,
finished: finished,
finishDropped: finishDropped
});
campaignRates[campaign].overall.started += started;
campaignRates[campaign].overall.finished += finished;
campaignRates[campaign].overall.startDropped += startDropped;
// Only finishDropped if on last level in campaign
if (campaigns[campaign].indexOf(level) === campaigns[campaign].length - 1) {
campaignRates[campaign].overall.finishDropped += finishDropped;
}
else {
campaignRates[campaign].overall.startDropped += finishDropped;
}
break;
}
}
}
// Sort level data by campaign order
for (campaign in campaignRates) {
campaignRates[campaign].levels.sort(function(a, b) {
if (campaigns[campaign].indexOf(a.level) < campaigns[campaign].indexOf(b.level)) return -1;
return 1;
});
}
print("\nCampaign drop off rates");
print("Where do players stop playing?");
print("Drop-off point: last start or finish level event.");
print("Columns: level, started the level, left after starting, finished level, left after finishing level");
for (campaign in campaigns) {
print("\n" + campaign);
var level = "level";
var levelSpacer = new Array(longestLevelName - level.length).join(' ');
print(level + levelSpacer + "started\tdropped\t\tfinished dropped");
for (var i = 0; i < campaignRates[campaign].levels.length; i++) {
var level = campaignRates[campaign].levels[i].level;
var started = campaignRates[campaign].levels[i].started;
var startDropped = campaignRates[campaign].levels[i].startDropped;
var finished = campaignRates[campaign].levels[i].finished;
var finishDropped = campaignRates[campaign].levels[i].finishDropped;
var levelSpacer = new Array(longestLevelName - level.length).join(' ');
print(level + levelSpacer + started + "\t" + (started < 100 ? "\t" : "") + startDropped + "\t" + (startDropped / started * 100).toFixed(2) + "%\t" + finished + "\t" + finishDropped + "\t" + (finishDropped / finished * 100).toFixed(2) + "%");
}
var level = 'Overall';
var started = campaignRates[campaign].overall.started;
var startDropped = campaignRates[campaign].overall.startDropped;
var finished = campaignRates[campaign].overall.finished;
var finishDropped = campaignRates[campaign].overall.finishDropped;
var levelSpacer = new Array(longestLevelName - level.length).join(' ');
print(level + levelSpacer + started + "\t" + (started < 100 ? "\t" : "") + startDropped + "\t" + (startDropped / started * 100).toFixed(2) + "%\t" + finished + "\t" + finishDropped + "\t" + (finishDropped / finished * 100).toFixed(2) + "%");
}

View file

@ -53,10 +53,6 @@ function getCompletionRates() {
}
}};
// TODO: sort by level, date,
// var sort = {$sort: { "_id.level" : 1, "_id.created" : -1}};
//var cursor = db['analytics.log.events'].aggregate(match, proj0, proj1, proj2, group, sort);
// var cursor = db['analytics.log.events'].aggregate(match, proj0, group, sort);
var cursor = db['analytics.log.events'].aggregate(match, proj0, group);
// <level><date><data>

View file

@ -86,10 +86,10 @@ class LinuxMongoDBDownloader(MongoDBDownloader):
@property
def download_url(self):
if self.dependency.config.mem_width == 64:
return u"http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-latest.tgz"
return u"http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.6.6.tgz"
else:
warnings.warn(u"MongoDB *really* doesn't run well on 32 bit systems. You have been warned.")
return u"http://fastdl.mongodb.org/linux/mongodb-linux-i686-latest.tgz"
return u"http://fastdl.mongodb.org/linux/mongodb-linux-i686-2.6.6.tgz"
class WindowsMongoDBDownloader(MongoDBDownloader):
@property
@ -97,13 +97,13 @@ class WindowsMongoDBDownloader(MongoDBDownloader):
#TODO: Implement Windows Vista detection
warnings.warn(u"If you have a version of Windows older than 7, MongoDB may not function properly!")
if self.dependency.config.mem_width == 64:
return u"http://fastdl.mongodb.org/win32/mongodb-win32-x86_64-2008plus-latest.zip"
return u"http://fastdl.mongodb.org/win32/mongodb-win32-x86_64-2008plus-2.6.6.zip"
else:
return u"http://fastdl.mongodb.org/win32/mongodb-win32-i386-latest.zip"
return u"http://fastdl.mongodb.org/win32/mongodb-win32-i386-2.6.6.zip"
class MacMongoDBDownloader(MongoDBDownloader):
@property
def download_url(self):
return u"http://fastdl.mongodb.org/osx/mongodb-osx-x86_64-latest.tgz"
return u"http://fastdl.mongodb.org/osx/mongodb-osx-x86_64-2.6.6.tgz"

View file

@ -3,8 +3,8 @@ The MIT License (MIT)
Copyright (c) 2014 CodeCombat Inc. and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in allcopies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN sCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THESOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,29 @@
_____ _ _____ _ _
/ __ \ | | / __ \ | | | |
| / \/ ___ __| | ___ | / \/ ___ _ __ ___ | |__ __ _| |_
| | / _ \ / _` |/ _ \ | | / _ \| '_ ` _ \| '_ \ / _` | __|
| \__/\ (_) | (_| | __/ | \__/\ (_) | | | | | | |_) | (_| | |_
\____/\___/ \__,_|\___| \____/\___/|_| |_| |_|_.__/ \__,_|\__|
=============================================================================
Felicidades, ahora eres parte dela comunidad de CodeCombat.
Ahora que el ambiente de desarrollo ha sido instalado, estas listo para comenzar
a contribuir y hacer este mundo un mejor lugar.
Tienes preguntas o te gustaria conocernos?
Habla con nosotros en hipchat @ https://www.hipchat.com/g3plnOKqa
Tambien puedes hablar con nosotros en nuestro foro.
El foro esta en @ http://discourse.codecombat.com/
Puedes leer sobre los ultimos cambios en nuestro blog.
El blog esta en @ http://blog.codecombat.com/
Por ultimo, puedes encontrar casi toda nuestra documentacion
e informacion en nuestra wiki @ https://github.com/codecombat/codecombat/wiki
Esperemos que disfrutes tanto la comunidad como nosotros
- Nick, George, Scott, Michael, Jeremy and Glen

View file

@ -14,7 +14,7 @@ contributing and help us make this world a better place.
Do you have questions or would you like to meet us?
Talk with us on hipchat @ https://www.hipchat.com/g3plnOKqa
Another way to reach is, is by visiting our forum.
Another way to reach us, is by visiting our forum.
You can find it @ http://discourse.codecombat.com/
You can read about the latest developments on our blog site.
@ -23,7 +23,7 @@ This one can be found @ http://blog.codecombat.com/
Last but not least, you can find most of our documentation
and information on our wiki @ https://github.com/codecombat/codecombat/wiki
We hope you'll enjoy yourself within our community, just as much as us.
We hope you enjoy yourself within our community, just as much as we do.
- Nick, George, Scott, Michael, Jeremy and Glen

View file

@ -0,0 +1,6 @@
1) Si tienes una pregunta, hazla con cuidado y detalles
2) Esta instalacion esta en beta y puede tener errores
3) Puedes reportar bugs en @ 'https://github.com/codecombat/codecombat/issues'
4) Tienes preguntas o sugerencias? Habla con nosotros en HipChat @ https://www.hipchat.com/g3plnOKqa
Puedes encontrar una guia paso a paso para esta instalacion en @ https://github.com/codecombat/codecombat/wiki/Setup-on-Windows:-a-step-by-step-guide

View file

@ -1,7 +1,6 @@
1) When there is a question, please answer carefull and correct
1) When there is a question, please answer carefully and correctly
2) This setup is still in beta and may contain bugs
3) You can report bugs @ 'https://github.com/codecombat/codecombat/issues'
4) Having questions/suggestions? Talk with us on HipChat via CodeCombat.com
4) Having questions/suggestions? Talk with us on HipChat @ https://www.hipchat.com/g3plnOKqa
You can find a step-by-step guide for this installation on our wiki.
github.com/codecombat/codecombat/wiki/Setup-on-Windows:-a-step-by-step-guide
You can find a step-by-step guide for this installation at @ https://github.com/codecombat/codecombat/wiki/Setup-on-Windows:-a-step-by-step-guide

View file

@ -47,7 +47,7 @@
<opensource>CodeCombat is opensource, like you already know.</opensource>
<online>All our sourcecode can be found online at Github.</online>
<manual>You can choose to do the entire Git setup yourself.</manual>
<norec>However we recommend that you instead let us handle it instead.</norec>
<norec>However we recommend that you let us handle it instead.</norec>
</intro>
<skip>
<question>Do you want to do the Local Git setup manually yourself?</question>
@ -69,7 +69,7 @@
<info>Please enter your github information, to configure your local repository.</info>
<username>Username: </username>
<password>Password: </password>
<process>Thank you... Configuring your local repistory right now...</process>
<process>Thank you... Configuring your local repository right now...</process>
</config>
</github>
<switch>
@ -82,7 +82,7 @@
<binstall>Installing bower packages...</binstall>
<sass>Installing sass...</sass>
<npm>Installing npm...</npm>
<brnch>Starting brunch....</brnch>
<brnch>Starting brunch...</brnch>
<mongodb>Setting up a MongoDB database for you...</mongodb>
<db>Downloading the last version of the CodeCombat database...</db>
<script>Preparing the automatic startup script for you...</script>
@ -105,4 +105,4 @@
<s5> 2) Now just open 'localhost:3000' in your prefered browser.</s5>
<s6>That's it, you're now ready to start working on CodeCombat!</s6>
</start>
</variables>
</variables>

View file

@ -1,5 +1,6 @@
AnalyticsLogEvent = require './AnalyticsLogEvent'
Handler = require '../commons/Handler'
log = require 'winston'
class AnalyticsLogEventHandler extends Handler
modelClass: AnalyticsLogEvent
@ -17,4 +18,280 @@ class AnalyticsLogEventHandler extends Handler
instance.set('user', req.user._id)
instance
getByRelationship: (req, res, args...) ->
return @getLevelCompletionsBySlugs(req, res) if args[1] is 'level_completions'
return @getCampaignDropOffs(req, res) if args[1] is 'campaign_drop_offs'
super(arguments...)
getLevelCompletionsBySlugs: (req, res) ->
# Returns an array of per-day level starts and finishes
# Parameters:
# slug - level slug
# startDay - Inclusive, optional, e.g. '2014-12-14'
# endDay - Exclusive, optional, e.g. '2014-12-16'
# TODO: An uncached call takes about 15s locally
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?
# Cache results for 1 day
@levelCompletionsCache ?= {}
@levelCompletionsCachedSince ?= new Date()
if (new Date()) - @levelCompletionsCachedSince > 86400 * 1000 # Dumb cache expiration
@levelCompletionsCache = {}
@levelCompletionsCachedSince = new Date()
cacheKey = levelSlug
cacheKey += 's' + startDay if startDay?
cacheKey += 'e' + endDay if endDay?
return @sendSuccess res, levelCompletions if levelCompletions = @levelCompletionsCache[cacheKey]
# Build query
match = {$match: {$and: [{$or: [{"event" : 'Started Level'}, {"event" : 'Saw Victory'}]}]}}
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, "event": 1, "level": {$ifNull: ["$properties.level", "$properties.levelID"]}, "created": {"$concat": [{"$substr": ["$created", 0, 4]}, "-", {"$substr": ["$created", 5, 2]}, "-", {"$substr" : ["$created", 8, 2]}]}}}
group = {"$group": {"_id": {"event": "$event", "created": "$created", "level": "$level"}, "count": {"$sum": 1}}}
query = AnalyticsLogEvent.aggregate match, project, group
query.exec (err, data) =>
if err? then return @sendDatabaseError res, err
# Build per-level-day started and finished counts
levelDateMap = {}
for item in data
created = item._id.created
event = item._id.event
level = item._id.level
continue unless level?
# 'Started Level' event uses level slug, 'Saw Victory' event uses level name with caps and spaces.
level = level.toLowerCase().replace new RegExp(' ', 'g'), '-' if event is 'Saw Victory'
levelDateMap[level] ?= {}
levelDateMap[level][created] ?= {}
levelDateMap[level][created] ?= {}
if event is 'Saw Victory'
levelDateMap[level][created]['finished'] = item.count
else
levelDateMap[level][created]['started'] = item.count
# Build list of level completions
# Cache every level, since we had to grab all this data anyway
completions = {}
for level of levelDateMap
completions[level] = []
for created, item of levelDateMap[level]
completions[level].push
level: level
created: created
started: item.started
finished: item.finished
cacheKey = level
cacheKey += 's' + startDay if startDay?
cacheKey += 'e' + endDay if endDay?
@levelCompletionsCache[cacheKey] = completions[level]
@sendSuccess res, completions[levelSlug]
getCampaignDropOffs: (req, res) ->
# Returns a dictionary of per-campaign level start and finish drop-offs
# Drop-off: last started or finished level event
# Parameters:
# slugs - array of campaign slugs
# startDay - Inclusive, optional, e.g. '2014-12-14'
# endDay - Exclusive, optional, e.g. '2014-12-16'
# TODO: Read per-campaign level progression data from a legit source
# TODO: An uncached call can take over 30s locally
# TODO: Returns all the campaigns
# TODO: Calculate overall campaign stats
campaignSlugs = req.query.slugs or req.body.slugs
startDay = req.query.startDay or req.body.startDay
endDay = req.query.endDay or req.body.endDay
return @sendSuccess res, [] unless campaignSlugs?
# Cache results for 1 day
@campaignDropOffsCache ?= {}
@campaignDropOffsCachedSince ?= new Date()
if (new Date()) - @campaignDropOffsCachedSince > 86400 * 1000 # Dumb cache expiration
@campaignDropOffsCache = {}
@campaignDropOffsCachedSince = new Date()
cacheKey = campaignSlugs.join(',')
cacheKey += 's' + startDay if startDay?
cacheKey += 'e' + endDay if endDay?
return @sendSuccess res, campaignDropOffs if campaignDropOffs = @campaignDropOffsCache[cacheKey]
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?
AnalyticsLogEvent.find(queryParams).select('created event properties user').exec (err, data) =>
if err? then return @sendDatabaseError res, err
# Bucketize events by user
userProgression = {}
for item in data
created = item.get('created')
event = item.get('event')
if event is 'Saw Victory'
level = item.get('properties.level').toLowerCase().replace new RegExp(' ', 'g'), '-'
else
level = item.get('properties.levelID')
continue unless level?
user = item.get('user')
userProgression[user] ?= []
userProgression[user].push
created: created
event: event
level: level
# Order user progression by created
for user in userProgression
userProgression[user].sort (a,b) -> if a.created < b.created then return -1 else 1
# Per-level start/drop/finish/drop
levelProgression = {}
for user of userProgression
for i in [0...userProgression[user].length]
event = userProgression[user][i].event
level = userProgression[user][i].level
levelProgression[level] ?=
started: 0
startDropped: 0
finished: 0
finishDropped: 0
if event is 'Started Level'
levelProgression[level].started++
levelProgression[level].startDropped++ if i is userProgression[user].length - 1
else if event is 'Saw Victory'
levelProgression[level].finished++
levelProgression[level].finishDropped++ if i is userProgression[user].length - 1
# Put in campaign order
campaignRates = {}
for level of levelProgression
for campaign of campaigns
if level in campaigns[campaign]
started = levelProgression[level].started
startDropped = levelProgression[level].startDropped
finished = levelProgression[level].finished
finishDropped = levelProgression[level].finishDropped
campaignRates[campaign] ?=
levels: []
# overall:
# started: 0,
# startDropped: 0,
# finished: 0,
# finishDropped: 0
campaignRates[campaign].levels.push
level: level
started: started
startDropped: startDropped
finished: finished
finishDropped: finishDropped
break
# Sort level data by campaign order
for campaign of campaignRates
campaignRates[campaign].levels.sort (a, b) ->
if campaigns[campaign].indexOf(a.level) < campaigns[campaign].indexOf(b.level) then return -1 else 1
# Return all campaign data for simplicity
# Cache other individual campaigns too, since we have them
@campaignDropOffsCache[cacheKey] = campaignRates
for campaign of campaignRates
cacheKey = campaign
cacheKey += 's' + startDay if startDay?
cacheKey += 'e' + endDay if endDay?
@campaignDropOffsCache[cacheKey] = campaignRates
@sendSuccess res, campaignRates
# Copied from WorldMapView
dungeonLevels = [
'dungeons-of-kithgard',
'gems-in-the-deep',
'shadow-guard',
'kounter-kithwise',
'crawlways-of-kithgard',
'forgetful-gemsmith',
'true-names',
'favorable-odds',
'the-raised-sword',
'haunted-kithmaze',
'riddling-kithmaze',
'descending-further',
'the-second-kithmaze',
'dread-door',
'known-enemy',
'master-of-names',
'lowly-kithmen',
'closing-the-distance',
'tactical-strike',
'the-final-kithmaze',
'the-gauntlet',
'kithgard-gates',
'cavern-survival'
];
forestLevels = [
'defense-of-plainswood',
'winding-trail',
'patrol-buster',
'endangered-burl',
'village-guard',
'thornbush-farm',
'back-to-back',
'ogre-encampment',
'woodland-cleaver',
'shield-rush',
'peasant-protection',
'munchkin-swarm',
'munchkin-harvest',
'swift-dagger',
'shrapnel',
'arcane-ally',
'touch-of-death',
'bonemender',
'coinucopia',
'copper-meadows',
'drop-the-flag',
'deadly-pursuit',
'rich-forager',
'siege-of-stonehold',
'multiplayer-treasure-grove',
'dueling-grounds'
];
desertLevels = [
'the-dunes',
'the-mighty-sand-yak',
'oasis',
'sarven-road',
'sarven-gaps',
'thunderhooves',
'medical-attention',
'minesweeper',
'sarven-sentry',
'keeping-time',
'hoarding-gold',
'decoy-drill',
'yakstraction',
'sarven-brawl',
'desert-combat',
'dust',
'mirage-maker',
'sarven-savior',
'odd-sandstorm'
];
campaigns = {
'dungeon': dungeonLevels,
'forest': forestLevels,
'desert': desertLevels
}
module.exports = new AnalyticsLogEventHandler()

View file

@ -72,6 +72,7 @@ LevelHandler = class LevelHandler extends Handler
return @getHistogramData(req, res, args[0]) if args[1] is 'histogram_data'
return @checkExistence(req, res, args[0]) if args[1] is 'exists'
return @getPlayCountsBySlugs(req, res) if args[1] is 'play_counts'
return @getLevelPlaytimesBySlugs(req, res) if args[1] is 'playtime_averages'
super(arguments...)
fetchLevelByIDAndHandleErrors: (id, req, res, callback) ->
@ -321,7 +322,7 @@ LevelHandler = class LevelHandler extends Handler
@playCountCachedSince ?= new Date()
if (new Date()) - @playCountCachedSince > 86400 * 1000 # Dumb cache expiration
@playCountCache = {}
@playCountCacheSince = new Date()
@playCountCachedSince = new Date()
cacheKey = levelIDs.join ','
if playCounts = @playCountCache[cacheKey]
return @sendSuccess res, playCounts
@ -340,4 +341,53 @@ LevelHandler = class LevelHandler extends Handler
return true if method is null or method is 'get'
super(req, document, method)
getLevelPlaytimesBySlugs: (req, res) ->
# Returns an array of per-day level average playtimes
# Parameters:
# slugs - array of level slugs
# startDay - Inclusive, optional, e.g. '2014-12-14'
# endDay - Exclusive, optional, e.g. '2014-12-16'
# TODO: An uncached call takes about 5s for dungeons-of-kithgard locally
# TODO: This is very similar to getLevelCompletionsBySlugs(), time to generalize analytics APIs?
levelSlugs = req.query.slugs or req.body.slugs
startDay = req.query.startDay or req.body.startDay
endDay = req.query.endDay or req.body.endDay
return @sendSuccess res, [] unless levelSlugs?
# Cache results for 1 day
@levelPlaytimesCache ?= {}
@levelPlaytimesCachedSince ?= new Date()
if (new Date()) - @levelPlaytimesCachedSince > 86400 * 1000 # Dumb cache expiration
@levelPlaytimesCache = {}
@levelPlaytimesCachedSince = new Date()
cacheKey = levelSlugs.join(',')
cacheKey += 's' + startDay if startDay?
cacheKey += 'e' + endDay if endDay?
return @sendSuccess res, levelPlaytimes if levelPlaytimes = @levelPlaytimesCache[cacheKey]
# 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]}]}}}
group = {"$group": {"_id": {"created": "$created", "level": "$levelID"}, "average": {"$avg": "$playtime"}}}
query = Session.aggregate match, project, group
query.exec (err, data) =>
if err? then return @sendDatabaseError res, err
# Build list of level average playtimes
playtimes = []
for item in data
playtimes.push
level: item._id.level
created: item._id.created
average: item.average
@levelPlaytimesCache[cacheKey] = playtimes
@sendSuccess res, playtimes
module.exports = new LevelHandler()

View file

@ -33,6 +33,7 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
'tier'
'extendedName'
'unlockLevelName'
'tasks'
]
hasAccess: (req) ->

View file

@ -9,9 +9,12 @@ User = require '../../../server/users/User'
# TODO: these tests have some rerun/cleanup issues
# TODO: add tests for purchase, payment, subscribe, unsubscribe, and earned achievements
# TODO: AnalyticsUsersActive collection isn't currently used.
# TODO: Will remove these tests if we end up ripping out the disabled saveActiveUser calls.
describe 'Analytics', ->
it 'registered user', (done) ->
xit 'registered user', (done) ->
clearModels [AnalyticsUsersActive], (err) ->
expect(err).toBeNull()
user = new User
@ -29,7 +32,7 @@ describe 'Analytics', ->
expect(activeUsers[0]?.get('event')).toEqual('register')
done()
it 'level completed', (done) ->
xit 'level completed', (done) ->
clearModels [AnalyticsUsersActive], (err) ->
expect(err).toBeNull()
unittest.getNormalJoe (joe) ->
@ -53,7 +56,7 @@ describe 'Analytics', ->
expect(activeUsers[0]?.get('event')).toEqual('level-completed/lotr')
done()
it 'level playtime', (done) ->
xit 'level playtime', (done) ->
clearModels [AnalyticsUsersActive], (err) ->
expect(err).toBeNull()
unittest.getNormalJoe (joe) ->