mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-14 05:55:00 -04:00
Merge branch 'master' into production
This commit is contained in:
commit
c92078997f
9 changed files with 353 additions and 359 deletions
app
core
locale
styles
templates/editor/campaign
views
server/analytics
|
@ -29,7 +29,7 @@ elementAcceptsKeystrokes = (el) ->
|
|||
# not radio, checkbox, range, or color
|
||||
return (tag is 'textarea' or (tag is 'input' and type in textInputTypes) or el.contentEditable in ['', 'true']) and not (el.readOnly or el.disabled)
|
||||
|
||||
COMMON_FILES = ['/images/pages/base/modal_background.png', '/images/level/popover_background.png'] #'/images/level/code_palette_wood_background.png', , '/images/level/code_editor_background.png'
|
||||
COMMON_FILES = ['/images/pages/base/modal_background.png', '/images/level/popover_background.png', '/images/level/code_palette_wood_background.png', '/images/level/code_editor_background_border.png']
|
||||
preload = (arrayOfImages) ->
|
||||
$(arrayOfImages).each ->
|
||||
$('<img/>')[0].src = @
|
||||
|
|
|
@ -680,55 +680,54 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
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!"
|
||||
# artisan_attribute_2: "A hankering to do a whole lot of testing and iteration. To make good levels, you need to take it to others and watch them play it, and be prepared to find a lot of things to fix."
|
||||
# artisan_attribute_3: "For the time being, endurance en par with an Adventurer. Our Level Editor is super preliminary and frustrating to use. You have been warned!"
|
||||
# artisan_join_desc: "Use the Level Editor in these steps, give or take:"
|
||||
# artisan_join_step1: "Read the documentation."
|
||||
# artisan_join_step2: "Create a new level and explore existing levels."
|
||||
# artisan_join_step3: "Find us in our public HipChat room for help."
|
||||
# artisan_join_step4: "Post your levels on the forum for feedback."
|
||||
# artisan_subscribe_desc: "Get emails on level editor updates and announcements."
|
||||
# adventurer_introduction: "Let's be clear about your role: you are the tank. You're going to take heavy damage. We need people to try out brand-new levels and help identify how to make things better. The pain will be enormous; making good games is a long process and no one gets it right the first time. If you can endure and have a high constitution score, then this class might be for you."
|
||||
# adventurer_attribute_1: "A thirst for learning. You want to learn how to code and we want to teach you how to code. You'll probably be doing most of the teaching in this case, though."
|
||||
# adventurer_attribute_2: "Charismatic. Be gentle but articulate about what needs improving, and offer suggestions on how to improve."
|
||||
# adventurer_join_pref: "Either get together with (or recruit!) an Artisan and work with them, or check the box below to receive emails when there are new levels to test. We'll also be posting about levels to review on our networks like"
|
||||
# adventurer_forum_url: "our forum"
|
||||
# adventurer_join_suf: "so if you prefer to be notified those ways, sign up there!"
|
||||
# adventurer_subscribe_desc: "Get emails when there are new levels to test."
|
||||
# scribe_introduction_pref: "CodeCombat isn't just going to be a bunch of levels. It will also include a resource for knowledge, a wiki of programming concepts that levels can hook into. That way rather than each Artisan having to describe in detail what a comparison operator is, they can simply link their level to the Article describing them that is already written for the player's edification. Something along the lines of what the "
|
||||
# scribe_introduction_url_mozilla: "Mozilla Developer Network"
|
||||
# scribe_introduction_suf: " has built. If your idea of fun is articulating the concepts of programming in Markdown form, then this class might be for you."
|
||||
# scribe_attribute_1: "Skill in words is pretty much all you need. Not only grammar and spelling, but able to convey complicated ideas to others."
|
||||
# contact_us_url: "Contact us"
|
||||
# scribe_join_description: "tell us a little about yourself, your experience with programming and what sort of things you'd like to write about. We'll go from there!"
|
||||
# scribe_subscribe_desc: "Get emails about article writing announcements."
|
||||
# diplomat_introduction_pref: "So, if there's one thing we learned from the "
|
||||
# diplomat_launch_url: "launch in October"
|
||||
# diplomat_introduction_suf: "it's that there is sizeable interest in CodeCombat in other countries! We're building a corps of translators eager to turn one set of words into another set of words to get CodeCombat as accessible across the world as possible. If you like getting sneak peeks at upcoming content and getting these levels to your fellow nationals ASAP, then this class might be for you."
|
||||
# diplomat_attribute_1: "Fluency in English and the language you would like to translate to. When conveying complicated ideas, it's important to have a strong grasp in both!"
|
||||
# diplomat_i18n_page_prefix: "You can start translating our levels by going to our"
|
||||
# diplomat_i18n_page: "translations page"
|
||||
# diplomat_i18n_page_suffix: ", or our interface and website on GitHub."
|
||||
# diplomat_join_pref_github: "Find your language locale file "
|
||||
# diplomat_github_url: "on GitHub"
|
||||
# diplomat_join_suf_github: ", edit it online, and submit a pull request. Also, check this box below to keep up-to-date on new internationalization developments!"
|
||||
# diplomat_subscribe_desc: "Get emails about i18n developments and levels to translate."
|
||||
# ambassador_introduction: "This is a community we're building, and you are the connections. We've got forums, emails, and social networks with lots of people to talk with and help get acquainted with the game and learn from. If you want to help people get involved and have fun, and get a good feel of the pulse of CodeCombat and where we're going, then this class might be for you."
|
||||
# ambassador_attribute_1: "Communication skills. Be able to identify the problems players are having and help them solve them. Also, keep the rest of us informed about what players are saying, what they like and don't like and want more of!"
|
||||
# ambassador_join_desc: "tell us a little about yourself, what you've done and what you'd be interested in doing. We'll go from there!"
|
||||
# ambassador_join_note_strong: "Note"
|
||||
# ambassador_join_note_desc: "One of our top priorities is to build multiplayer where players having difficulty solving levels can summon higher level wizards to help them. This will be a great way for ambassadors to do their thing. We'll keep you posted!"
|
||||
# ambassador_subscribe_desc: "Get emails on support updates and multiplayer developments."
|
||||
# changes_auto_save: "Changes are saved automatically when you toggle checkboxes."
|
||||
# diligent_scribes: "Our Diligent Scribes:"
|
||||
# powerful_archmages: "Our Powerful Archmages:"
|
||||
# creative_artisans: "Our Creative Artisans:"
|
||||
# brave_adventurers: "Our Brave Adventurers:"
|
||||
# translating_diplomats: "Our Translating Diplomats:"
|
||||
# helpful_ambassadors: "Our Helpful Ambassadors:"
|
||||
artisan_introduction_suf: ", entonces esta lase es ideal para ti."
|
||||
artisan_attribute_1: "Cualquier experiencia creando contenido similar estaría bien, como por ejemplo el editor de niveles de Blizzard. ¡Aunque no es necesaria!"
|
||||
artisan_attribute_2: "Un anhelo de hacer un montón de pruebas e iteraciones. Para hacer buenos niveles necesitas mostrárselos a otros y mirar como juegan, además de estar preparado para encontrar los fallos a reparar."
|
||||
artisan_attribute_3: "Por el momento, la resistencia va a la par con el Aventurero. Nuestro editor de niveles está a un nivel de desarrollo temprano y puede ser muy frustrante usarlo. ¡Estás advertido!"
|
||||
artisan_join_desc: "Sigue las siguientes indicaciones para usar el editor de niveles. Tómalo o déjalo:"
|
||||
artisan_join_step1: "Lee la documentación."
|
||||
artisan_join_step2: "Crea un nuevo nivel y explora los niveles existentes."
|
||||
artisan_join_step3: "Busca nuestra sala pública de HipChat en busca de ayuda."
|
||||
artisan_join_step4: "Publica tus niveles en el foro para recibir comentarios críticos."
|
||||
artisan_subscribe_desc: "Recibe correos sobre actualizaciones del editor de niveles y anuncios."
|
||||
adventurer_introduction: "Hablemos claro sobre tu papel: eres el tanque. Vas a recibir fuertes daños. Necesitamos gente para probar nuestros flamantes niveles y ayudar a mejorarlos. El dolor será enorme; hacer buenos juegos es un proceso largo y nadie lo consigue a la primera. Si puedes resistir y tener una puntuación alta en resistencia, entonces esta clase es para ti."
|
||||
adventurer_attribute_1: "Estar sediento de conocimientos. Quieres aprender a programar y nosotros queremos enseñarte a hacerlo. Aunque en este caso es más probable que seas tú el que esté haciendo la mayor parte de la enseñanza."
|
||||
adventurer_attribute_2: "Carismático. Se amable pero claro a la hora de desglosar qué necesita ser mejorado y sugiere de qué formas podría hacerse."
|
||||
adventurer_join_pref: "Reúnete con (¡o recluta!) un Artesano y trabaja con ellos, o marca la casilla de abajo para recibir un correo cuando haya nuevos niveles para probar. También publicaremos en nuestras redes nuevos niveles para revisar"
|
||||
adventurer_forum_url: "nuestro foro"
|
||||
adventurer_join_suf: "así que si prefieres estar informado en esa forma, ¡crea una cuenta allí!"
|
||||
adventurer_subscribe_desc: "Recibe correos cuando haya nuevos niveles para testar."
|
||||
scribe_introduction_pref: "CodeCombat no será solo un montón de niveles. También será una fuente de conocimientos, una wiki de conceptos de programación a la que los niveles se engancharan. De esa forma, en lugar de que cada Artesano tenga que describir en detalle qué es un operador de comparación, podrá simplemente enlazar el nivel al Artículo que los describe y que ya ha sido escrito para edificación del jugador. Algo en la línea de lo que la "
|
||||
scribe_introduction_url_mozilla: "Mozilla Developer Network"
|
||||
scribe_introduction_suf: " ha construido. Si tu idea de diversión es articular los conceptos de la programación de una forma sencilla, entonces esta clase es para ti."
|
||||
scribe_attribute_1: "Habilidad a la hora de escribir es casi todo lo que necesitas. No solo dominar la gramática y la ortografía sino también expresar ideas complicadas a los demás de forma sencilla."
|
||||
contact_us_url: "Escribenos un correo electrónico"
|
||||
scribe_join_description: "cuéntanos más sobre ti, tu experiencia en el mundo de la programación y sobre qué cosas te gustaría escribir. ¡Y continuaremos a partir de ahí!"
|
||||
scribe_subscribe_desc: "Recibe correos sobre anuncios de redacción de Artículos."
|
||||
diplomat_introduction_pref: "Así, si hemos aprendido algo desde el "
|
||||
diplomat_launch_url: "lanzamiento en octubre"
|
||||
diplomat_introduction_suf: "hay un interés considerable en CodeCombat en otros paises, ¡especialmente Brasil! Estamos formando un cuerpo de traductores con ganas de traducir un grupo de palabras tras otro para hacer CodeCombat tan accesible para todo el mundo como sea posible. Si quieres recibir avances de próximos contenidos y quieres poner esos niveles a disposición de los que comparten tu idioma tan pronto como sea posible, entonces esta Clase es para ti."
|
||||
diplomat_attribute_1: "Fluidez con el ingles y el lenguaje al que quieras traducir. Cuando de transmitir ideas complejas se trata, ¡es importante tener grandes conocimientos de ambas!"
|
||||
diplomat_i18n_page_prefix: "Puedes traducir nuestros niveles yendo a nuestra"
|
||||
diplomat_i18n_page: "página de traducciones"
|
||||
diplomat_i18n_page_suffix: ", o en nuestra interfaz y sitio web en GitHub."
|
||||
diplomat_join_pref_github: "Encuentra el fichero local de tu idioma "
|
||||
diplomat_github_url: "en GitHub"
|
||||
diplomat_join_suf_github: ", edítalo online, y solicita que sea revisado. Además, marca la casilla de abajo para mantenerte informado en nuevos progresos en Internacionalización."
|
||||
diplomat_subscribe_desc: "Recibe correos sobre nuevos niveles y desarrollos para traducir."
|
||||
ambassador_introduction: "Esta es una comunidad en construcción y tú eres parte de las conexiones. Tenemos chat Olark, correos electrónicos y las redes sociales con una gran cantidad de personas con quienes hablar, ayudar a familiarizarse con el juego y aprender. Si quieres ayudar a la gente a que se involucre, se divierta, y tenga buenas sensaciones sobre CodeCombat y hacia dónde vamos, entonces esta clase es para ti."
|
||||
ambassador_attribute_1: "Habilidades de comunicación. Ser capaz de identificar los problemas que los jugadores están teniendo y ayudarles a resolverlos. Además, mantener al resto de nosotros informados sobre lo que los jugadores están diciendo, lo que les gusta, lo que no ¡y de lo que quieren más!"
|
||||
ambassador_join_desc: "cuéntanos más sobre ti, que has hecho y qué estarías interesado en hacer. ¡Y continuaremos a partir de ahí!"
|
||||
ambassador_join_note_strong: "Nota"
|
||||
ambassador_join_note_desc: "Una de nuestras principales prioridades es construir un modo multijugador donde los jugadores con mayores dificultades a la hora de resolver un nivel, puedan invocar a los magos más avanzados para que les ayuden. Será una buena manera de que los Embajadores puedan hacer su trabajo. ¡Te mantendremos informado!"
|
||||
ambassador_subscribe_desc: "Recibe correos sobre actualizaciones de soporte y desarrollo del multijugador."
|
||||
changes_auto_save: "Los cambios son guardados automáticamente cuando marcas las casillas de verificación."
|
||||
diligent_scribes: "Nuestros diligentes Escribas:"
|
||||
powerful_archmages: "Nuestros poderosos Archimagos:"
|
||||
creative_artisans: "Nuestros creativos Artesanos:"
|
||||
brave_adventurers: "Nuestros bravos Aventureros:"
|
||||
translating_diplomats: "Nuestros políglotas Diplomáticos:"
|
||||
helpful_ambassadors: "Nuestros amables Embajadores:"
|
||||
|
||||
ladder:
|
||||
please_login: "Por favor inicia sesión antes de jugar una partida de escalera."
|
||||
|
@ -836,7 +835,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
cost: "Costo"
|
||||
next_payment: "Próximo Pago"
|
||||
card: "Tarjeta"
|
||||
status_unsubscribed_active: "No estas suscripto y no se te cobrará, pero tu cuenta estar activa por ahora."
|
||||
status_unsubscribed_active: "No estas suscripto y no se te cobrará, pero tu cuenta está activa por ahora."
|
||||
status_unsubscribed: "Obtén acceso a nuevos niveles, heroés, items y gemas extras con la suscripción a CodeCombat!"
|
||||
|
||||
loading_error:
|
||||
|
@ -866,27 +865,27 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
user_schema: "Esquema de Usuario"
|
||||
user_profile: "Perfil de Usuario"
|
||||
patches: "Parches"
|
||||
# patched_model: "Source Document"
|
||||
patched_model: "Documento fuente"
|
||||
model: "Modelo"
|
||||
system: "Sistema"
|
||||
systems: "Sistemas"
|
||||
component: "Componente"
|
||||
components: "Componentes"
|
||||
# thang: "Thang"
|
||||
# thangs: "Thangs"
|
||||
thang: "Thang"
|
||||
thangs: "Thangs"
|
||||
level_session: "Tu Sesión"
|
||||
opponent_session: "Sesión del Oponente"
|
||||
article: "Artícule"
|
||||
user_names: "Nombres de usuario"
|
||||
# thang_names: "Thang Names"
|
||||
thang_names: "Nombres de thang"
|
||||
files: "Archivos"
|
||||
# top_simulators: "Top Simulators"
|
||||
# source_document: "Source Document"
|
||||
top_simulators: "Mejores simuladores"
|
||||
source_document: "Documento fuente"
|
||||
document: "Documento"
|
||||
# sprite_sheet: "Sprite Sheet"
|
||||
employers: "Empleadores"
|
||||
candidates: "Candidatos"
|
||||
# candidate_sessions: "Candidate Sessions"
|
||||
candidate_sessions: "Sesión de candidato"
|
||||
# user_remark: "User Remark"
|
||||
# user_remarks: "User Remarks"
|
||||
versions: "Versiones"
|
||||
|
@ -894,7 +893,7 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
heroes: "Héroes"
|
||||
achievement: "Logros"
|
||||
# clas: "CLAs"
|
||||
# play_counts: "Play Counts"
|
||||
play_counts: "Conteo de juegos"
|
||||
feedback: "Feedback"
|
||||
payment_info: "Información de pago"
|
||||
|
||||
|
@ -928,12 +927,12 @@ module.exports = nativeDescription: "Español (América Latina)", englishDescrip
|
|||
opensource_description_center: "y ayudanos si quieres! CodeCombat esta construido por docenas de proyectos open source, y los amamos. Mira "
|
||||
archmage_wiki_url: "nuestra wiki de Archimago wiki"
|
||||
opensource_description_suffix: "Para la lista de softwares que hacen al juego posible."
|
||||
# practices_title: "Respectful Best Practices"
|
||||
practices_title: "Mejores prácticas respetuosas"
|
||||
# practices_description: "These are our promises to you, the player, in slightly less legalese."
|
||||
privacy_title: "Privacidad"
|
||||
privacy_description: "No vederemos nada sobre tu información personalWe will not sell any of your personal information."
|
||||
security_title: "Seguridad"
|
||||
# security_description: "We strive to keep your personal information safe. As an open source project, our site is freely open to anyone to review and improve our security systems."
|
||||
security_description: "Queremos mantener tu información personal privada. Como un proyecto open source, cualquiera puede revisar y mejorar nuestros sistemas de seguridad."
|
||||
email_title: "Mail"
|
||||
email_description_prefix: "No te vamos a inundar de Spam. Mediante"
|
||||
email_settings_url: "tus opciones de mail"
|
||||
|
|
|
@ -52,11 +52,11 @@ module.exports = nativeDescription: "Македонски", englishDescription:
|
|||
play_as: "Играј како" # Ladder page
|
||||
spectate: "Набљудувај" # Ladder page
|
||||
players: "играчи" # Hover over a level on /play
|
||||
hours_played: "часови изиграни" # 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
|
||||
owned: "Имаш" # For items you own
|
||||
locked: "Заклучено"
|
||||
purchasable: "Може да се купи" # For a hero you unlocked but haven't purchased
|
||||
available: "Достапно"
|
||||
|
@ -142,7 +142,7 @@ module.exports = nativeDescription: "Македонски", englishDescription:
|
|||
save: "Зачувај"
|
||||
publish: "Објави"
|
||||
create: "Направи"
|
||||
manual: "Прирачник"
|
||||
manual: "Рачно"
|
||||
# fork: "Fork"
|
||||
play: "Играј" # When used as an action verb, like "Play next level"
|
||||
retry: "Обиди се повторно"
|
||||
|
@ -162,14 +162,14 @@ module.exports = nativeDescription: "Македонски", englishDescription:
|
|||
version: "Верзија"
|
||||
submitter: "Подносител"
|
||||
submitted: "Поднесено"
|
||||
# commit_msg: "Commit Message"
|
||||
commit_msg: "Порака за поднесокот"
|
||||
# review: "Review"
|
||||
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"
|
||||
select_changes: "Одбери две промени подолу за да ја видиш разликата."
|
||||
undo: "Врати (Ctrl+Z)"
|
||||
redo: "Повтори (Ctrl+Shift+Z)"
|
||||
play_preview: "Пушти преглед на моменталното ниво"
|
||||
result: "Резултат"
|
||||
results: "Резултати"
|
||||
description: "Опис"
|
||||
|
@ -179,17 +179,17 @@ module.exports = nativeDescription: "Македонски", englishDescription:
|
|||
password: "Лозинка"
|
||||
message: "Порака"
|
||||
# code: "Code"
|
||||
# ladder: "Ladder"
|
||||
ladder: "Ранг листа"
|
||||
when: "Кога"
|
||||
opponent: "Противник"
|
||||
# rank: "Rank"
|
||||
rank: "Ранг"
|
||||
# score: "Score"
|
||||
# win: "Win"
|
||||
# loss: "Loss"
|
||||
# tie: "Tie"
|
||||
# easy: "Easy"
|
||||
# medium: "Medium"
|
||||
# hard: "Hard"
|
||||
win: "Победа"
|
||||
loss: "Изгубено"
|
||||
tie: "Нерешено"
|
||||
easy: "Лесно"
|
||||
medium: "Средно"
|
||||
hard: "Тешко"
|
||||
player: "Играч"
|
||||
# player_level: "Level" # Like player level 5, not like level: Dungeons of Kithgard
|
||||
|
||||
|
@ -238,7 +238,7 @@ module.exports = nativeDescription: "Македонски", englishDescription:
|
|||
victory_sign_up: "Направи сметка за да го зачуваш напредокот"
|
||||
victory_sign_up_poke: "Сакаш да го зачуваш твојот код? Направи бесплатна сметка!"
|
||||
victory_rate_the_level: "Оцени го нивото: " # Only in old-style levels.
|
||||
# victory_return_to_ladder: "Return to Ladder"
|
||||
victory_return_to_ladder: "Врати се кај ранг листата"
|
||||
victory_play_continue: "Продолжи"
|
||||
victory_saving_progress: "Напредокот се зачувува"
|
||||
victory_go_home: "Оди дома" # Only in old-style levels.
|
||||
|
@ -260,54 +260,54 @@ module.exports = nativeDescription: "Македонски", englishDescription:
|
|||
# tome_see_all_methods: "See all methods you can edit" # Title text for method list selector (shown when there are multiple programmable methdos).
|
||||
# tome_select_a_thang: "Select Someone for "
|
||||
# tome_available_spells: "Available Spells"
|
||||
# tome_your_skills: "Your Skills"
|
||||
# tome_help: "Help"
|
||||
tome_your_skills: "Твои вештини"
|
||||
tome_help: "Помош"
|
||||
# tome_current_method: "Current Method"
|
||||
# hud_continue_short: "Continue"
|
||||
# code_saved: "Code Saved"
|
||||
# skip_tutorial: "Skip (esc)"
|
||||
hud_continue_short: "Продолжи"
|
||||
code_saved: "Кодот е зачуван"
|
||||
skip_tutorial: "Прескокни (esc)"
|
||||
# keyboard_shortcuts: "Key Shortcuts"
|
||||
# loading_ready: "Ready!"
|
||||
# loading_start: "Start Level"
|
||||
# problem_alert_title: "Fix Your Code"
|
||||
# problem_alert_help: "Help"
|
||||
# time_current: "Now:"
|
||||
# time_total: "Max:"
|
||||
# time_goto: "Go to:"
|
||||
# infinite_loop_try_again: "Try Again"
|
||||
loading_ready: "Готово!"
|
||||
loading_start: "Почни ниво"
|
||||
problem_alert_title: "Поправи си го кодот"
|
||||
problem_alert_help: "Помош"
|
||||
time_current: "Сега:"
|
||||
time_total: "Максимум:"
|
||||
time_goto: "Оди до:"
|
||||
infinite_loop_try_again: "Обиди се повторно"
|
||||
# infinite_loop_reset_level: "Reset Level"
|
||||
# infinite_loop_comment_out: "Comment Out My Code"
|
||||
# tip_toggle_play: "Toggle play/paused with Ctrl+P."
|
||||
# tip_scrub_shortcut: "Ctrl+[ and Ctrl+] rewind and fast-forward."
|
||||
# tip_guide_exists: "Click the guide, inside game menu (at the top of the page), for useful info."
|
||||
tip_toggle_play: "Пушти/Паузирај со Ctrl+P."
|
||||
tip_scrub_shortcut: "Ctrl+[ и Ctrl+] премотуваат назад и напред."
|
||||
tip_guide_exists: "Кликни на водичот, во менито (на врвот на страната), за корисни информации."
|
||||
# tip_open_source: "CodeCombat is 100% open source!"
|
||||
# tip_beta_launch: "CodeCombat launched its beta in October, 2013."
|
||||
# tip_think_solution: "Think of the solution, not the problem."
|
||||
# tip_theory_practice: "In theory, there is no difference between theory and practice. But in practice, there is. - Yogi Berra"
|
||||
# tip_error_free: "There are two ways to write error-free programs; only the third one works. - Alan Perlis"
|
||||
# tip_debugging_program: "If debugging is the process of removing bugs, then programming must be the process of putting them in. - Edsger W. Dijkstra"
|
||||
# tip_forums: "Head over to the forums and tell us what you think!"
|
||||
tip_beta_launch: "CodeCombat ја пушти својата бета верзија во октомври, 2013."
|
||||
tip_think_solution: "Мисли на решението, не на проблемот."
|
||||
tip_theory_practice: "Теоретски, не постои разлика помеѓу теорија и пракса. Но, во пракса, постои. - Yogi Berra"
|
||||
tip_error_free: "Постојат два начина за пишување програми без грешки; само третиот работи. - Alan Perlis"
|
||||
tip_debugging_program: "Ако дебагирање е процесот на отстранување грешки, тогаш програмирање мора да е процесот при кој тие настануваат. - Edsger W. Dijkstra"
|
||||
tip_forums: "Упати се кон форумите и кажи ни што мислиш!"
|
||||
# tip_baby_coders: "In the future, even babies will be Archmages."
|
||||
# tip_morale_improves: "Loading will continue until morale improves."
|
||||
tip_morale_improves: "Вчитувањето ќе продолжи се додека моралот не се подобри."
|
||||
# tip_all_species: "We believe in equal opportunities to learn programming for all species."
|
||||
# tip_reticulating: "Reticulating spines."
|
||||
# tip_harry: "Yer a Wizard, "
|
||||
# tip_great_responsibility: "With great coding skill comes great debug responsibility."
|
||||
tip_great_responsibility: "Со големи програмерски вештини доаѓа и голема одговорност за дебагирање."
|
||||
# tip_munchkin: "If you don't eat your vegetables, a munchkin will come after you while you're asleep."
|
||||
# tip_binary: "There are only 10 types of people in the world: those who understand binary, and those who don't."
|
||||
# tip_commitment_yoda: "A programmer must have the deepest commitment, the most serious mind. ~ Yoda"
|
||||
# tip_no_try: "Do. Or do not. There is no try. - Yoda"
|
||||
tip_binary: "Има само 10 вида на луѓе во светот: оние кои што разбираат бинарно, и оние кои не разбираат."
|
||||
tip_commitment_yoda: "Програмер мора да ја има најдлабоката обврзаност, најсериозниот ум. ~ Yoda"
|
||||
tip_no_try: "Направи. Или не прави. Нема обидување. - Yoda"
|
||||
# tip_patience: "Patience you must have, young Padawan. - Yoda"
|
||||
# tip_documented_bug: "A documented bug is not a bug; it is a feature."
|
||||
# tip_impossible: "It always seems impossible until it's done. - Nelson Mandela"
|
||||
# tip_talk_is_cheap: "Talk is cheap. Show me the code. - Linus Torvalds"
|
||||
# tip_first_language: "The most disastrous thing that you can ever learn is your first programming language. - Alan Kay"
|
||||
# tip_hardware_problem: "Q: How many programmers does it take to change a light bulb? A: None, it's a hardware problem."
|
||||
# tip_hofstadters_law: "Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law."
|
||||
# tip_premature_optimization: "Premature optimization is the root of all evil. - Donald Knuth"
|
||||
tip_documented_bug: "Документирана грешка не е грешка; тоа е едноставно нешто што програмот го прави."
|
||||
tip_impossible: "Секогаш изледа невозможно, се додека некој не го направи. - Nelson Mandela"
|
||||
tip_talk_is_cheap: "Зборувањето е евтино. Покажи ми го кодот. - Linus Torvalds"
|
||||
tip_first_language: "Нај катастрофалната работа што можеш да ја научиш е твојот прв програмски јазик. - Alan Kay"
|
||||
tip_hardware_problem: "Прашање: Колку програмери се потребни за да се смени сијалица? Одговор: Ниту еден, тоа е хардверски проблем."
|
||||
tip_hofstadters_law: "Законот на Hofstadter: Секогаш треба повеќе време отколку што очекуваш, дури и кога ќе го земеш во предвид законот на Hofstadter."
|
||||
tip_premature_optimization: "Предвремената оптимизација е коренот на сето зло. - Donald Knuth"
|
||||
# tip_brute_force: "When in doubt, use brute force. - Ken Thompson"
|
||||
# tip_extrapolation: "There are only two kinds of people: those that can extrapolate from incomplete data..."
|
||||
# tip_superpower: "Coding is the closest thing we have to a superpower."
|
||||
tip_extrapolation: "Има само два вида на луѓе: оние кои можат да екстраполираат од некомплетни податоци..."
|
||||
tip_superpower: "Програмирањето е способност, најблиска до супермоќ, која ја имаме."
|
||||
|
||||
# game_menu:
|
||||
# inventory_tab: "Inventory"
|
||||
|
@ -339,16 +339,16 @@ module.exports = nativeDescription: "Македонски", englishDescription:
|
|||
# equip: "Equip"
|
||||
# unequip: "Unequip"
|
||||
|
||||
# buy_gems:
|
||||
# few_gems: "A few gems"
|
||||
# pile_gems: "Pile of gems"
|
||||
# chest_gems: "Chest of gems"
|
||||
# purchasing: "Purchasing..."
|
||||
# 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"
|
||||
buy_gems:
|
||||
few_gems: "Неколку скапоцени камења"
|
||||
pile_gems: "Купче скапоцени камења"
|
||||
chest_gems: "Сандак скапоцени камења"
|
||||
purchasing: "Купувам..."
|
||||
declined: "Твојата картичка беше одбиена"
|
||||
retrying: "Серверска грешка, се обидувам повторно."
|
||||
prompt_title: "Немаш доволно скапоцени камења"
|
||||
prompt_body: "Дали сакаш да земеш повеќе?"
|
||||
prompt_button: "Влези во продавницата"
|
||||
# recovered: "Previous gems purchase recovered. Please refresh the page."
|
||||
|
||||
# subscribe:
|
||||
|
|
|
@ -22,11 +22,11 @@
|
|||
right: 0
|
||||
width: 75%
|
||||
|
||||
#analytics-button
|
||||
position: absolute
|
||||
right: 1%
|
||||
top: 1%
|
||||
padding: 3px 8px
|
||||
#analytics-button
|
||||
position: absolute
|
||||
right: 1%
|
||||
top: 1%
|
||||
padding: 3px 8px
|
||||
|
||||
#analytics-modal .modal-content
|
||||
background-color: white
|
||||
#analytics-modal .modal-content
|
||||
background-color: white
|
||||
|
|
|
@ -186,6 +186,9 @@ $gameControlMargin: 30px
|
|||
.tooltip
|
||||
z-index: 2
|
||||
|
||||
.tooltip-arrow
|
||||
display: none
|
||||
|
||||
.level-info-container
|
||||
display: none
|
||||
position: absolute
|
||||
|
|
|
@ -75,5 +75,7 @@ block outer_content
|
|||
td= campaignDropOffs.levels[i].finished
|
||||
td= campaignDropOffs.levels[i].finishDropped
|
||||
td= campaignDropOffs.levels[i].finishDropRate
|
||||
else
|
||||
button.btn.btn-default.disabled#analytics-button Analytics Loading...
|
||||
|
||||
block footer
|
||||
|
|
|
@ -262,7 +262,7 @@ module.exports = class CampaignEditorView extends RootView
|
|||
# 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]}
|
||||
data: {startDay: startDay, slug: @campaignHandle}
|
||||
method: 'POST'
|
||||
success: success
|
||||
}, 0
|
||||
|
|
|
@ -93,16 +93,30 @@ module.exports = class InventoryModal extends ModalView
|
|||
locked = not (item.get('original') in me.items())
|
||||
#locked = false if me.get('slug') is 'nick'
|
||||
|
||||
required = item.get('original') in _.flatten(_.values(@options.level.get('requiredGear') ? {}))
|
||||
restricted = item.get('original') in _.flatten(_.values(@options.level.get('restrictedGear') ? {}))
|
||||
allRestrictedGear = _.flatten(_.values(@options.level.get('restrictedGear') ? {}))
|
||||
restricted = item.get('original') in allRestrictedGear
|
||||
|
||||
# TODO: make this re-use result of computation of updateLevelRequiredItems, which we can only do after heroClass is ready...
|
||||
requiredToPurchase = false
|
||||
if requiredGear = @options.level.get('requiredGear')
|
||||
inCampaignView = $('#campaign-view').length
|
||||
unless gearSlugs[item.get('original')] is 'tarnished-bronze-breastplate' and inCampaignView and @options.level.get('slug') is 'the-raised-sword'
|
||||
for slot in item.getAllowedSlots()
|
||||
continue unless requiredItems = requiredGear[slot]
|
||||
continue if @equipment[slot] and @equipment[slot] not in allRestrictedGear
|
||||
# Point out that they must buy it if they haven't bought any of the required items for that slot, and it's the first one.
|
||||
if item.get('original') is requiredItems[0] and not _.find(requiredItems, (requiredItem) -> me.ownsItem requiredItem)
|
||||
requiredToPurchase = true
|
||||
break
|
||||
|
||||
placeholder = not item.getFrontFacingStats().props.length and not _.size(item.getFrontFacingStats().stats)
|
||||
|
||||
if placeholder and locked # The item is not complete, so don't put it into a collection.
|
||||
null
|
||||
else if locked and required
|
||||
else if locked and requiredToPurchase
|
||||
item.classes.push 'locked'
|
||||
@itemGroups.requiredPurchaseItems.add item
|
||||
else if locked and item.get('slug') isnt 'simple-boots'
|
||||
else if locked
|
||||
item.classes.push 'locked'
|
||||
if item.isSilhouettedItem() or not item.get('gems')
|
||||
# Don't even load/show these--don't add to a collection. (Bandwidth optimization.)
|
||||
|
@ -363,66 +377,66 @@ module.exports = class InventoryModal extends ModalView
|
|||
config
|
||||
|
||||
requireLevelEquipment: ->
|
||||
# This is temporary, until we have a more general way of awarding items and configuring required/restricted items per level.
|
||||
requiredGear = @options.level.get('requiredGear') ? {}
|
||||
restrictedGear = @options.level.get('restrictedGear') ? {}
|
||||
if @inserted
|
||||
if @supermodel.finished()
|
||||
equipment = @getCurrentEquipmentConfig() # Make sure @equipment is updated
|
||||
else
|
||||
equipment = @equipment
|
||||
hadRequired = @remainingRequiredEquipment?.length
|
||||
@remainingRequiredEquipment = []
|
||||
@$el.find('.should-equip').removeClass('should-equip')
|
||||
inWorldMap = $('#world-map-view').length
|
||||
if @supermodel.finished() and heroClass = @selectedHero?.get('heroClass')
|
||||
for slot, item of _.clone equipment
|
||||
itemModel = @items.findWhere original: item
|
||||
unless itemModel and heroClass in itemModel.classes
|
||||
console.log 'Unequipping', itemModel.get('heroClass'), 'item', itemModel.get('name'), 'from slot due to class restrictions.'
|
||||
@unequipItemFromSlot @$el.find(".item-slot[data-slot='#{slot}']")
|
||||
delete equipment[slot]
|
||||
for slot, items of restrictedGear
|
||||
items = [items] if _.isString items
|
||||
for item in items
|
||||
item = gear[item] unless item.length is 24 # Temp: until migration to DB data is done
|
||||
equipped = equipment[slot]
|
||||
if equipped and equipped is item
|
||||
console.log 'Unequipping restricted item', equipped, 'for', slot, 'before level', @options.level.get('slug')
|
||||
@unequipItemFromSlot @$el.find(".item-slot[data-slot='#{slot}']")
|
||||
delete equipment[slot]
|
||||
for slot, items of requiredGear
|
||||
items = [items] if _.isString items # Temp: until migration to arrays is done
|
||||
item = items[0] # TODO: look for the last one that they own, or the first one if they don't own any.
|
||||
# TODO: require them to have one of the given items, not just either the item or anything except all these exceptions.
|
||||
slug = gearSlugs[item]
|
||||
if item.length isnt 24 # Temp: until migration to DB data is done
|
||||
[item, slug] = [gear[item], item]
|
||||
#console.log 'requiring', item, slug, 'for', slot, 'and have', equipment[slot]
|
||||
if (slot in ['right-hand', 'left-hand', 'head', 'torso']) and not (heroClass is 'Warrior' or
|
||||
(heroClass is 'Ranger' and @options.level.get('slug') in ['swift-dagger', 'shrapnel']) or
|
||||
(heroClass is 'Wizard' and @options.level.get('slug') in ['touch-of-death', 'bonemender'])) and not (slug in ['crude-builders-hammer', 'wooden-builders-hammer'])
|
||||
# After they switch to a ranger or wizard, we stop being so finicky about class-specific gear.
|
||||
continue
|
||||
continue if slug is 'tarnished-bronze-breastplate' and inWorldMap and @options.level.get('slug') is 'the-raised-sword' # Don't tell them they need it until they need it in the level
|
||||
# This is called frequently to make sure the player isn't using any restricted items and knows she must equip any required items.
|
||||
return unless @inserted
|
||||
equipment = if @supermodel.finished() then @getCurrentEquipmentConfig() else @equipment # Make sure we're using latest equipment.
|
||||
hadRequired = @remainingRequiredEquipment?.length
|
||||
@remainingRequiredEquipment = []
|
||||
@$el.find('.should-equip').removeClass('should-equip')
|
||||
@unequipClassRestrictedItems equipment
|
||||
@unequipLevelRestrictedItems equipment
|
||||
@updateLevelRequiredItems equipment
|
||||
if hadRequired and not @remainingRequiredEquipment.length
|
||||
@endHighlight()
|
||||
@highlightElement '#play-level-button', duration: 5000
|
||||
$('#play-level-button').prop('disabled', @remainingRequiredEquipment.length > 0)
|
||||
|
||||
unequipClassRestrictedItems: (equipment) ->
|
||||
return unless @supermodel.finished() and heroClass = @selectedHero?.get 'heroClass'
|
||||
for slot, item of _.clone equipment
|
||||
itemModel = @items.findWhere original: item
|
||||
unless itemModel and heroClass in itemModel.classes
|
||||
console.log 'Unequipping', itemModel.get('heroClass'), 'item', itemModel.get('name'), 'from slot due to class restrictions.'
|
||||
@unequipItemFromSlot @$el.find(".item-slot[data-slot='#{slot}']")
|
||||
delete equipment[slot]
|
||||
|
||||
unequipLevelRestrictedItems: (equipment) ->
|
||||
return unless restrictedGear = @options.level.get 'restrictedGear'
|
||||
for slot, items of restrictedGear
|
||||
for item in items
|
||||
equipped = equipment[slot]
|
||||
continue if equipped and not (
|
||||
(slug is 'crude-builders-hammer' and equipped in [gear['simple-sword'], gear['long-sword'], gear['sharpened-sword'], gear['roughedge']]) or
|
||||
(slug in ['simple-sword', 'long-sword', 'roughedge', 'sharpened-sword'] and equipped is gear['crude-builders-hammer']) or
|
||||
(slug is 'leather-boots' and equipped is gear['simple-boots']) or
|
||||
(slug is 'simple-boots' and equipped is gear['leather-boots'])
|
||||
)
|
||||
itemModel = @items.findWhere {slug: slug}
|
||||
continue unless itemModel
|
||||
availableSlotSelector = "#unequipped .item[data-item-id='#{itemModel.id}']"
|
||||
@highlightElement availableSlotSelector, delay: 500, sides: ['right'], rotation: Math.PI / 2
|
||||
@$el.find(availableSlotSelector).addClass 'should-equip'
|
||||
@$el.find("#equipped div[data-slot='#{slot}']").addClass 'should-equip'
|
||||
@remainingRequiredEquipment.push slot: slot, item: item
|
||||
if hadRequired and not @remainingRequiredEquipment.length
|
||||
@endHighlight()
|
||||
@highlightElement '#play-level-button', duration: 5000
|
||||
$('#play-level-button').prop('disabled', @remainingRequiredEquipment.length > 0)
|
||||
if equipped and equipped is item
|
||||
console.log 'Unequipping restricted item', equipped, 'for', slot, 'before level', @options.level.get('slug')
|
||||
@unequipItemFromSlot @$el.find(".item-slot[data-slot='#{slot}']")
|
||||
delete equipment[slot]
|
||||
|
||||
updateLevelRequiredItems: (equipment) ->
|
||||
return unless requiredGear = @options.level.get 'requiredGear'
|
||||
return unless heroClass = @selectedHero?.get 'heroClass'
|
||||
for slot, items of requiredGear when items.length
|
||||
equipped = equipment[slot]
|
||||
continue if equipped in items
|
||||
continue if equipped # Actually, just let them play if they have equipped anything in that slot (and we haven't unequipped it due to restrictions).
|
||||
items = (item for item in items when heroClass in (@items.findWhere(original: item)?.classes ? []))
|
||||
continue unless items.length # If the required items are for another class, then let's not be finicky.
|
||||
|
||||
# We will point out the last (best) element that they own and can use, otherwise the first (cheapest).
|
||||
bestOwnedItem = _.findLast items, (item) -> me.ownsItem item
|
||||
item = bestOwnedItem ? items[0]
|
||||
|
||||
# For the Tarnished Bronze Breastplate only, don't tell them they need it until they need it in the level, so we can show how to buy it.
|
||||
slug = gearSlugs[item]
|
||||
inCampaignView = $('#campaign-view').length
|
||||
continue if slug is 'tarnished-bronze-breastplate' and inCampaignView and @options.level.get('slug') is 'the-raised-sword'
|
||||
|
||||
# Now we're definitely requiring and pointing out an item.
|
||||
itemModel = @items.findWhere {original: item}
|
||||
availableSlotSelector = "#unequipped .item[data-item-id='#{itemModel.id}']"
|
||||
@highlightElement availableSlotSelector, delay: 500, sides: ['right'], rotation: Math.PI / 2
|
||||
@$el.find(availableSlotSelector).addClass 'should-equip'
|
||||
@$el.find("#equipped div[data-slot='#{slot}']").addClass 'should-equip'
|
||||
@remainingRequiredEquipment.push slot: slot, item: item
|
||||
null
|
||||
|
||||
setHero: (@selectedHero) ->
|
||||
if @selectedHero.loading
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
AnalyticsLogEvent = require './AnalyticsLogEvent'
|
||||
Campaign = require '../campaigns/Campaign'
|
||||
Level = require '../levels/Level'
|
||||
Handler = require '../commons/Handler'
|
||||
log = require 'winston'
|
||||
|
||||
|
@ -103,16 +105,17 @@ class AnalyticsLogEventHandler extends Handler
|
|||
# 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: Must be a better way to organize this series of 3 database calls (campaigns, levels, analytics)
|
||||
# TODO: An uncached call can take over 30s locally
|
||||
# TODO: Returns all the campaigns
|
||||
# TODO: Calculate overall campaign stats
|
||||
# TODO: Assumes db campaign levels are in progression order. Should build this based on actual progression.
|
||||
|
||||
campaignSlugs = req.query.slugs or req.body.slugs
|
||||
campaignSlug = 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 campaignSlugs?
|
||||
return @sendSuccess res, [] unless campaignSlug?
|
||||
|
||||
# Cache results for 1 day
|
||||
@campaignDropOffsCache ?= {}
|
||||
|
@ -120,178 +123,151 @@ class AnalyticsLogEventHandler extends Handler
|
|||
if (new Date()) - @campaignDropOffsCachedSince > 86400 * 1000 # Dumb cache expiration
|
||||
@campaignDropOffsCache = {}
|
||||
@campaignDropOffsCachedSince = new Date()
|
||||
cacheKey = campaignSlugs.join(',')
|
||||
cacheKey = campaignSlug
|
||||
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?
|
||||
calculateDropOffs = (campaigns) =>
|
||||
# Calculate campaign drop off rates
|
||||
# Input:
|
||||
# campaigns - per-campaign dictionary of ordered level slugs
|
||||
|
||||
AnalyticsLogEvent.find(queryParams).select('created event properties user').exec (err, data) =>
|
||||
if err? then return @sendDatabaseError res, err
|
||||
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?
|
||||
|
||||
# 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
|
||||
AnalyticsLogEvent.find(queryParams).select('created event properties user').exec (err, data) =>
|
||||
if err? then return @sendDatabaseError res, err
|
||||
|
||||
# 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?
|
||||
# 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
|
||||
@sendSuccess res, 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'
|
||||
];
|
||||
getLevelData = (campaigns, campaignLevelIDs) =>
|
||||
# Get level data and replace levelIDs with level slugs in campaigns
|
||||
# Input:
|
||||
# campaigns - per-campaign dictionary of ordered levelIDs
|
||||
# campaignLevelIDs - dictionary of all campaign levelIDs
|
||||
# Output:
|
||||
# campaigns - per-campaign dictionary of ordered level slugs
|
||||
|
||||
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'
|
||||
];
|
||||
Level.find({original: {$in: campaignLevelIDs}, "version.isLatestMajor": true, "version.isLatestMinor": true}).exec (err, documents) =>
|
||||
if err? then return @sendDatabaseError res, err
|
||||
|
||||
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'
|
||||
];
|
||||
levelSlugMap = {}
|
||||
for doc in documents
|
||||
levelID = doc.get('original')
|
||||
levelSlug = doc.get('name').toLowerCase().replace new RegExp(' ', 'g'), '-'
|
||||
levelSlugMap[levelID] = levelSlug
|
||||
|
||||
campaigns = {
|
||||
'dungeon': dungeonLevels,
|
||||
'forest': forestLevels,
|
||||
'desert': desertLevels
|
||||
}
|
||||
# Replace levelIDs with level slugs
|
||||
for campaign of campaigns
|
||||
mapFn = (item) -> levelSlugMap[item]
|
||||
campaigns[campaign] = _.map campaigns[campaign], mapFn, @
|
||||
# Forest campaign levels are reversed for some reason
|
||||
campaigns[campaign].reverse() if campaign is 'forest'
|
||||
|
||||
calculateDropOffs campaigns
|
||||
|
||||
getCampaignData = () =>
|
||||
# Get campaign data
|
||||
# Output:
|
||||
# campaigns - per-campaign dictionary of ordered levelIDs
|
||||
# campaignLevelIDs - dictionary of all campaign levelIDs
|
||||
|
||||
Campaign.find().exec (err, documents) =>
|
||||
if err? then return @sendDatabaseError res, err
|
||||
|
||||
campaigns = {}
|
||||
levelCampaignMap = {}
|
||||
campaignLevelIDs = []
|
||||
for doc in documents
|
||||
campaignSlug = doc.get('slug')
|
||||
levels = doc.get('levels')
|
||||
campaigns[campaignSlug] = []
|
||||
levelCampaignMap[campaignSlug] = {}
|
||||
for levelID of levels
|
||||
campaigns[campaignSlug].push levelID
|
||||
campaignLevelIDs.push levelID
|
||||
levelCampaignMap[levelID] = campaignSlug
|
||||
|
||||
getLevelData campaigns, campaignLevelIDs
|
||||
|
||||
getCampaignData()
|
||||
|
||||
module.exports = new AnalyticsLogEventHandler()
|
||||
|
|
Loading…
Add table
Reference in a new issue