mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-27 06:23:41 -04:00
Merge branch 'master' into production
This commit is contained in:
commit
bc8e0945a1
25 changed files with 565 additions and 222 deletions
README.md
app
assets/images/pages/about
lib/surface
locale
templates
views
account
contribute
editor/thang
play
scripts/analytics/mongodb/queries
server
test/server/functional
|
@ -69,3 +69,10 @@ Whether you're novice or pro, the CodeCombat team is ready to help you implement
|
|||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
|
BIN
app/assets/images/pages/about/jose_small.png
Normal file
BIN
app/assets/images/pages/about/jose_small.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 12 KiB |
BIN
app/assets/images/pages/about/oleg_small.png
Normal file
BIN
app/assets/images/pages/about/oleg_small.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 9.3 KiB |
BIN
app/assets/images/pages/about/pavel_small.png
Normal file
BIN
app/assets/images/pages/about/pavel_small.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 10 KiB |
|
@ -1,6 +1,6 @@
|
|||
SpriteBuilder = require 'lib/sprites/SpriteBuilder'
|
||||
|
||||
floors = ['Dungeon Floor', 'Indoor Floor', 'Grass', 'Grass01', 'Grass02', 'Grass03', 'Grass04', 'Grass05', 'Goal Trigger', 'Obstacle', 'Sand 01', 'Sand 02', 'Sand 03', 'Sand 04', 'Sand 05', 'Sand 06', 'Talus 1', 'Talus 2', 'Talus 3', 'Talus 4', 'Talus 5', 'Talus 6', 'Firn 1', 'Firn 2', 'Firn 3', 'Firn 4', 'Firn 5', 'Firn 6']
|
||||
floors = ['Dungeon Floor', 'Indoor Floor', 'Grass', 'Grass01', 'Grass02', 'Grass03', 'Grass04', 'Grass05', 'Goal Trigger', 'Obstacle', 'Sand 01', 'Sand 02', 'Sand 03', 'Sand 04', 'Sand 05', 'Sand 06', 'Talus 1', 'Talus 2', 'Talus 3', 'Talus 4', 'Talus 5', 'Talus 6', 'Firn 1', 'Firn 2', 'Firn 3', 'Firn 4', 'Firn 5', 'Firn 6', 'Ice Rink 1', 'Ice Rink 2', 'Ice Rink 3']
|
||||
|
||||
module.exports = class SingularSprite extends createjs.Sprite
|
||||
childMovieClips: null
|
||||
|
|
|
@ -260,62 +260,62 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
|
|||
victory_review: "Fortæl os mere!" # Only in old-style levels.
|
||||
victory_hour_of_code_done: "Er du færdig?"
|
||||
victory_hour_of_code_done_yes: "Ja, jeg er færdig med min Kodetime!"
|
||||
# victory_experience_gained: "XP Gained"
|
||||
# victory_gems_gained: "Gems Gained"
|
||||
# victory_new_item: "New Item"
|
||||
# victory_viking_code_school: "Holy smokes, that was a hard level you just beat! If you aren't already a software developer, you should be. You just got fast-tracked for acceptance with Viking Code School, where you can take your skills to the next level and become a professional web developer in 14 weeks."
|
||||
# victory_become_a_viking: "Become a Viking"
|
||||
victory_experience_gained: "XP tjent"
|
||||
victory_gems_gained: "Diamanter tjent"
|
||||
victory_new_item: "Nyt udstyr"
|
||||
victory_viking_code_school: "For dælen det var en svær bane du lige slog! Hvis ikke du allerede er softwareudvikler, så burde du blive det. Du er lige kommet foran i køen til at blive accepteret hos Viking Code School, du kan tage dine evner til det næste niveau og blive en professionel webudvikler på 14 uger."
|
||||
victory_become_a_viking: "Bliv en Viking"
|
||||
guide_title: "Instruktioner"
|
||||
# tome_minion_spells: "Your Minions' Spells" # Only in old-style levels.
|
||||
# tome_read_only_spells: "Read-Only Spells" # Only in old-style levels.
|
||||
tome_minion_spells: "Dine Minions' besværgelser" # Only in old-style levels.
|
||||
tome_read_only_spells: "Læsebesværgelser" # Only in old-style levels.
|
||||
tome_other_units: "Andre enheder" # Only in old-style levels.
|
||||
# tome_cast_button_run: "Run"
|
||||
# tome_cast_button_running: "Running"
|
||||
# tome_cast_button_ran: "Ran"
|
||||
# tome_submit_button: "Submit"
|
||||
# tome_reload_method: "Reload original code for this method" # Title text for individual method reload button.
|
||||
# tome_select_method: "Select a Method"
|
||||
# tome_see_all_methods: "See all methods you can edit" # Title text for method list selector (shown when there are multiple programmable methods).
|
||||
tome_cast_button_run: "Kør"
|
||||
tome_cast_button_running: "Kører"
|
||||
tome_cast_button_ran: "Kørt"
|
||||
tome_submit_button: "Indsend"
|
||||
tome_reload_method: "Genindlæs den originale kode til denne metode" # Title text for individual method reload button.
|
||||
tome_select_method: "Vælg en metode"
|
||||
tome_see_all_methods: "Se alle metoder du kan redigere" # Title text for method list selector (shown when there are multiple programmable methods).
|
||||
tome_select_a_thang: "Vælg nogen til at "
|
||||
tome_available_spells: "Tilgængelige trylleformularer"
|
||||
# tome_your_skills: "Your Skills"
|
||||
# tome_help: "Help"
|
||||
# tome_current_method: "Current Method"
|
||||
# hud_continue_short: "Continue"
|
||||
# code_saved: "Code Saved"
|
||||
tome_your_skills: "Dine evner"
|
||||
tome_help: "Hjælp"
|
||||
tome_current_method: "Nuværende metode"
|
||||
hud_continue_short: "Fortsæt"
|
||||
code_saved: "Kode gemt"
|
||||
skip_tutorial: "Spring over (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:"
|
||||
# non_user_code_problem_title: "Unable to Load Level"
|
||||
# infinite_loop_title: "Infinite Loop Detected"
|
||||
# infinite_loop_description: "The initial code to build the world never finished running. It's probably either really slow or has an infinite loop. Or there might be a bug. You can either try running this code again or reset the code to the default state. If that doesn't fix it, please let us know."
|
||||
# check_dev_console: "You can also open the developer console to see what might be going wrong."
|
||||
# check_dev_console_link: "(instructions)"
|
||||
# infinite_loop_try_again: "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: "Use Ctrl+[ and Ctrl+] to rewind and fast-forward."
|
||||
# tip_guide_exists: "Click the guide, inside game menu (at the top of the page), for useful info."
|
||||
# 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_baby_coders: "In the future, even babies will be Archmages."
|
||||
# tip_morale_improves: "Loading will continue until morale improves."
|
||||
# tip_all_species: "We believe in equal opportunities to learn programming for all species."
|
||||
keyboard_shortcuts: "Tastaturgenveje"
|
||||
loading_ready: "Klar!"
|
||||
loading_start: "Start bane"
|
||||
problem_alert_title: "Ret din kode"
|
||||
problem_alert_help: "Hjælp"
|
||||
time_current: "Nu:"
|
||||
time_total: "Max:"
|
||||
time_goto: "Gå til:"
|
||||
non_user_code_problem_title: "Kan ikke indlæse banen"
|
||||
infinite_loop_title: "Uendelig løkke detekteret"
|
||||
infinite_loop_description: "Den indledende kode til at bygge verdenen blev aldrig færdig med at køre. Den er sandsynligvis enten meget langsom eller har en uendeligt løkke. Eller også er der en bug. Du kan enten prøve at køre denne kode igen eller nulstille koden til den oprindelige tilstand. Hvis ikke det virker må du meget gerne fortælle os det."
|
||||
check_dev_console: "Du kan også åbne udviklerkonsollen for at se hvad der kunne være galt."
|
||||
check_dev_console_link: "(vejledning)"
|
||||
infinite_loop_try_again: "Prøv igen"
|
||||
infinite_loop_reset_level: "Nulstil bane"
|
||||
infinite_loop_comment_out: "Udkommenter min kode"
|
||||
tip_toggle_play: "Skift mellem afspil/pause med Ctrl+P."
|
||||
tip_scrub_shortcut: "Brug Ctrl+[ og Ctrl+] til at spole tilbage og frem."
|
||||
tip_guide_exists: "Klik på guiden i spilmenuen (i toppen af siden) for brugbar info."
|
||||
tip_open_source: "CodeCombat er 100% open source!"
|
||||
tip_beta_launch: "CodeCombat søsatte sin beta i oktober, 2013."
|
||||
tip_think_solution: "Tænk på løsningen, ikke problemet."
|
||||
tip_theory_practice: "I teorien er der ingen forskel på teori og praksis. Men i praksis er der. - Yogi Bjørn"
|
||||
tip_error_free: "Der findes to måder at skrive fejlfrie programmer; kun den tredje virker. - Alan Perlis"
|
||||
tip_debugging_program: "Hvis debugging er at fjerne kodefejl, så må programmering være at proppe fejl ind i koden. - Edsger W. Dijkstra"
|
||||
tip_forums: "Kig over i vores forum og fortæl os hvad du synes!"
|
||||
tip_baby_coders: "I fremtiden vil selv babier være Ærketroldmænd."
|
||||
tip_morale_improves: "Indlæsning vil fortsætte indtil moralen forbedres."
|
||||
tip_all_species: "Vi tror på lige muligheder for at lære programmering for alle arter."
|
||||
# tip_reticulating: "Reticulating spines."
|
||||
# tip_harry: "Yer a Wizard, "
|
||||
# tip_great_responsibility: "With great coding skill comes great debug responsibility."
|
||||
tip_harry: "Du' en troldmand, "
|
||||
tip_great_responsibility: "Med store kodeevner kommer stort fejlfindingsansvnar."
|
||||
# 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"
|
||||
|
|
|
@ -568,6 +568,10 @@
|
|||
why_paragraph_2_suffix: "That's why CodeCombat is a multiplayer game, not a gamified lesson course. We won't stop until you can't stop--but this time, that's a good thing."
|
||||
why_paragraph_3: "If you're going to get addicted to some game, get addicted to this one and become one of the wizards of the tech age."
|
||||
press_title: "Bloggers/Press"
|
||||
contact_title: "Contact"
|
||||
codecombat_inc: "CodeCombat, Inc."
|
||||
address_part_1: "188 King St #507"
|
||||
address_part_2: "San Francisco, CA 94107"
|
||||
press_paragraph_1_prefix: "Want to write about us? Feel free to download and use all of the resources included in our"
|
||||
press_paragraph_1_link: "press packet"
|
||||
press_paragraph_1_suffix: ". All logos and images may be used without contacting us directly."
|
||||
|
|
|
@ -170,7 +170,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
accepted: "Aceptado"
|
||||
rejected: "Rechazado"
|
||||
withdrawn: "Retirado"
|
||||
# submitter: "Submitter"
|
||||
submitter: "Submitter"
|
||||
submitted: "Enviado"
|
||||
commit_msg: "Mensaje de Asignación o Commit"
|
||||
review: "Revisión"
|
||||
|
@ -262,9 +262,9 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
victory_hour_of_code_done_yes: "Si, ¡He terminado con mi hora de código!"
|
||||
victory_experience_gained: "XP Conseguida"
|
||||
victory_gems_gained: "Gemas Conseguidas"
|
||||
# victory_new_item: "New Item"
|
||||
victory_new_item: "Nuevo artículo"
|
||||
# victory_viking_code_school: "Holy smokes, that was a hard level you just beat! If you aren't already a software developer, you should be. You just got fast-tracked for acceptance with Viking Code School, where you can take your skills to the next level and become a professional web developer in 14 weeks."
|
||||
# victory_become_a_viking: "Become a Viking"
|
||||
victory_become_a_viking: "Convertirse en un vikingo"
|
||||
guide_title: "Guía"
|
||||
tome_minion_spells: "Los hechizos de tus súbditos" # Only in old-style levels.
|
||||
tome_read_only_spells: "Hechizos de solo lectura" # Only in old-style levels.
|
||||
|
@ -292,8 +292,8 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
time_current: "Ahora:"
|
||||
time_total: "Máx:"
|
||||
time_goto: "Ir a:"
|
||||
# non_user_code_problem_title: "Unable to Load Level"
|
||||
# infinite_loop_title: "Infinite Loop Detected"
|
||||
non_user_code_problem_title: "No puede cargar un nivel"
|
||||
infinite_loop_title: "Bucle infinito detectado"
|
||||
# infinite_loop_description: "The initial code to build the world never finished running. It's probably either really slow or has an infinite loop. Or there might be a bug. You can either try running this code again or reset the code to the default state. If that doesn't fix it, please let us know."
|
||||
# check_dev_console: "You can also open the developer console to see what might be going wrong."
|
||||
# check_dev_console_link: "(instructions)"
|
||||
|
@ -332,7 +332,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
tip_extrapolation: "Existen solo dos clases de personas: aquellos que pueden extrapolar desde información incompleta..."
|
||||
tip_superpower: "Programar es lo más parecido que tenemos a un superpoder."
|
||||
tip_control_destiny: "En el verdadero open source, tienes el derecho de controlar tu propio destino. - Linus Torvalds"
|
||||
# tip_no_code: "No code is faster than no code."
|
||||
tip_no_code: "Ningún código es más rápido que ningún código"
|
||||
tip_code_never_lies: "El código nunca os miente, los comentarios algunas veces. — Ron Jeffries"
|
||||
tip_reusable_software: "Antes de que el software pueda ser reutilizable, primero debe ser utilizable."
|
||||
tip_optimization_operator: "Cada lenguaje tiene un operator para optimización. En la mayoría de los lenguajes dicho operador es ‘//’"
|
||||
|
@ -346,7 +346,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
# tip_open_source_contribute: "You can help CodeCombat improve!"
|
||||
tip_recurse: "Iterar es humano, recursar es divino. - L. Peter Deutsch"
|
||||
# tip_free_your_mind: "You have to let it all go, Neo. Fear, doubt, and disbelief. Free your mind. - Morpheus"
|
||||
# tip_strong_opponents: "Even the strongest of opponents always has a weakness. - Itachi Uchiha"
|
||||
tip_strong_opponents: "Incluso el más fuerte de los opositores oculta debilidad. - Itachi Uchiha"
|
||||
|
||||
game_menu:
|
||||
inventory_tab: "Inventario"
|
||||
|
@ -366,14 +366,14 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
|
|||
auth_caption: "Salvar tu progreso."
|
||||
|
||||
leaderboard:
|
||||
# leaderboard: "Leaderboard"
|
||||
leaderboard: "Jefe de la liga"
|
||||
view_other_solutions: "Ver Otras Soluciones" # {change}
|
||||
scores: "Puntuaciones"
|
||||
# top_players: "Top Players by"
|
||||
day: "Hoy"
|
||||
week: "Esta semana"
|
||||
# all: "All-Time"
|
||||
# time: "Time"
|
||||
time: "Tiempo"
|
||||
damage_taken: "Daño recibido"
|
||||
damage_dealt: "Daño causado"
|
||||
difficulty: "Difficultad"
|
||||
|
|
|
@ -158,7 +158,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
|
|||
unwatch: "Ne plus regarder"
|
||||
submit_patch: "Soumettre un correctif"
|
||||
submit_changes: "Soumettre des Changements"
|
||||
# save_changes: "Save Changes"
|
||||
save_changes: "Sauvegarder les modifications"
|
||||
|
||||
general:
|
||||
and: "et"
|
||||
|
@ -263,7 +263,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
|
|||
victory_experience_gained: "XP gagnée"
|
||||
victory_gems_gained: "Gemmes gagnées"
|
||||
victory_new_item: "Nouvel item"
|
||||
# victory_viking_code_school: "Holy smokes, that was a hard level you just beat! If you aren't already a software developer, you should be. You just got fast-tracked for acceptance with Viking Code School, where you can take your skills to the next level and become a professional web developer in 14 weeks."
|
||||
victory_viking_code_school: "Par la barbe d'Odin, c'est un niveau difficile que tu viens de compléter! Si tu n'est pas un développeur de logiciel, tu devrais l'être ! Ceci vient de te propulser dans l'école de Code Vikings où tu pourras amener tes habilités au prochain niveau et devenir un développer web profesionnel en deux semaines."
|
||||
victory_become_a_viking: "Devenez un viking"
|
||||
guide_title: "Guide"
|
||||
tome_minion_spells: "Les sorts de vos soldats" # Only in old-style levels.
|
||||
|
@ -455,24 +455,24 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
|
|||
was_free_until: "Vous aviez un abonnement gratuit jusqu'à"
|
||||
managed_subs: "Gestion des abonnements"
|
||||
managed_subs_desc: "Ajout d'abonnements pour les autres joueurs (étudiants,enfants,etc.) for other players."
|
||||
# managed_subs_desc_2: "Recipients must have a CodeCombat account associated with the email address you provide."
|
||||
# group_discounts: "Group discounts"
|
||||
# group_discounts_1: "We also offer group discounts for bulk subscriptions."
|
||||
# group_discounts_1st: "1st subscription"
|
||||
# group_discounts_full: "Full price"
|
||||
# group_discounts_2nd: "Subscriptions 2-11"
|
||||
# group_discounts_20: "20% off"
|
||||
# group_discounts_12th: "Subscriptions 12+"
|
||||
# group_discounts_40: "40% off"
|
||||
# subscribing: "Subscribing..."
|
||||
# recipient_emails_placeholder: "Enter email address to subscribe, one per line."
|
||||
# subscribe_users: "Subscribe Users"
|
||||
# users_subscribed: "Users subscribed:"
|
||||
# no_users_subscribed: "No users subscribed, please double check your email addresses."
|
||||
# current_recipients: "Current Recipients"
|
||||
# unsubscribing: "Unsubscribing..."
|
||||
# subscribe_prepaid: "Click Subscribe to use prepaid code"
|
||||
# using_prepaid: "Using prepaid code for monthly subscription"
|
||||
managed_subs_desc_2: "Recipients must have a CodeCombat account associated with the email address you provide."
|
||||
group_discounts: "Rabais de groupes"
|
||||
group_discounts_1: "Nous offrons des rabais de groupe pour les gros abonnements"
|
||||
group_discounts_1st: "Premier abonnement"
|
||||
group_discounts_full: "Plein prix"
|
||||
group_discounts_2nd: "Abonnements 2-11"
|
||||
group_discounts_20: "Rabais de 20%"
|
||||
group_discounts_12th: "Abonnements 12+"
|
||||
group_discounts_40: "Rabais de 40%"
|
||||
subscribing: "S'inscrit..."
|
||||
recipient_emails_placeholder: "Entrez votre courriel pour vous abonner, un par ligne."
|
||||
subscribe_users: "Seulement pour les usagers aboonés"
|
||||
users_subscribed: "Usagers abonnés:"
|
||||
no_users_subscribed: "Aucun usager abonnés, veuillez vérifier vos courriels."
|
||||
current_recipients: "Recipients courant"
|
||||
unsubscribing: "Desincription en cours..."
|
||||
subscribe_prepaid: "Cliquer S'abonner pour utiliser du code prépayé"
|
||||
using_prepaid: "Utiliser le code prépayé pour un abonnement mensuel"
|
||||
|
||||
choose_hero:
|
||||
choose_hero: "Choisissez votre héros"
|
||||
|
@ -500,14 +500,14 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
|
|||
blocks: "Absorbe" # As in "this shield blocks this much damage"
|
||||
backstab: "Poignardé" # As in "this dagger does this much backstab damage"
|
||||
skills: "Compétences"
|
||||
# attack_1: "Deals"
|
||||
# attack_2: "of listed"
|
||||
# attack_3: "weapon damage."
|
||||
# health_1: "Gains"
|
||||
# health_2: "of listed"
|
||||
# health_3: "armor health."
|
||||
# speed_1: "Moves at"
|
||||
# speed_2: "meters per second."
|
||||
attack_1: "Inflige"
|
||||
attack_2: "Classé de"
|
||||
attack_3: "Dommage causé par l'arme"
|
||||
health_1: "Gains"
|
||||
health_2: "Provenance de la liste"
|
||||
health_3: "Endurance de l'armure"
|
||||
speed_1: "Se mouvoit à"
|
||||
speed_2: "mètres par seconde"
|
||||
available_for_purchase: "Disponible à l'achat" # Shows up when you have unlocked, but not purchased, a hero in the hero store
|
||||
level_to_unlock: "Niveau à débloquer :" # Label for which level you have to beat to unlock a particular hero (click a locked hero in the store to see)
|
||||
restricted_to_certain_heroes: "Seulement certains héros peuvent jouer ce niveau."
|
||||
|
@ -581,13 +581,13 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
|
|||
michael_blurb: "Sys Admin"
|
||||
matt_title: "Programmeur" # {change}
|
||||
matt_blurb: "Bicycliste"
|
||||
# cat_title: "Chief Artisan"
|
||||
# cat_blurb: "Airbender"
|
||||
# josh_title: "Game Designer"
|
||||
# josh_blurb: "Floor Is Lava"
|
||||
# jose_title: "Music"
|
||||
# jose_blurb: "Taking Off"
|
||||
# retrostyle_title: "Illustration"
|
||||
cat_title: "Chef Artisan"
|
||||
cat_blurb: "Seigneur de l'air"
|
||||
josh_title: "Designer de jeu"
|
||||
josh_blurb: "Le plancher est de la lave"
|
||||
jose_title: "Musique"
|
||||
jose_blurb: "Décollage"
|
||||
retrostyle_title: "Illustration"
|
||||
# retrostyle_blurb: "RetroStyle Games"
|
||||
|
||||
teachers:
|
||||
|
@ -595,7 +595,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
|
|||
intro_1: "CodeCombat est un jeu en ligne qui enseigne la programmation. Les élèves écrivent du code dans de vrais langages de programmation."
|
||||
intro_2: "Aucune expérience requise !"
|
||||
free_title: "Combien cela coûte-t-il ?"
|
||||
# cost_china: "CodeCombat in China is free for the first five levels, after which it costs $9.99 USD per month for access to our other 140+ levels on our exclusive China servers."
|
||||
cost_china: "CodeCombat en Chine est gratuit pour les cinq premiers niveaux,après le jeu coûte 9.99$ US par mois pour avoir un accès aux autres 140+ niveaux sur les serveurs exlcusifs chinois"
|
||||
free_1: "La version de base de CodeCombat est gratuite ! Il y a 70+ niveaux gratuits qui couvrent chaque concepts." # {change}
|
||||
free_2: "Un abonnement mensuel fournit l'accès à des vidéos de tutoriels ainsi qu'à des niveaux d'entraînement supplémentaires."
|
||||
teacher_subs_title: "Les enseignants reçoivent un abonnement gratuit !"
|
||||
|
@ -609,14 +609,14 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
|
|||
sub_includes_4: "Support email premium"
|
||||
sub_includes_5: "7 nouveaux héros avec des capacités uniques à maitriser"
|
||||
sub_includes_6: "3500 gemmes bonus chaque mois"
|
||||
# sub_includes_7: "Private Clans"
|
||||
# monitor_progress_title: "How do I monitor student progress?"
|
||||
# monitor_progress_1: "Student progress can be monitored by creating a"
|
||||
# monitor_progress_2: "for your class."
|
||||
# monitor_progress_3: "To add a student, send them the invite link for your Clan, which is on the"
|
||||
# monitor_progress_4: "page."
|
||||
# monitor_progress_5: "After they join, you will see a summary of the student's progress on your Clan's page."
|
||||
# private_clans_1: "Private Clans provide increased privacy and detailed progress information for each student."
|
||||
sub_includes_7: "Clans Privées"
|
||||
monitor_progress_title: "Comment puis-je faire pour surveiller les progrès des étudiants?"
|
||||
monitor_progress_1: "Le progès des étudiants peut être surveiller en créant un"
|
||||
monitor_progress_2: "pour votre classe"
|
||||
monitor_progress_3: "Pour ajouter un étudiant, envoyer leur le lien contenant une invitation pour votre Clan"
|
||||
monitor_progress_4: "page."
|
||||
monitor_progress_5: "After they join, you will see a summary of the student's progress on your Clan's page."
|
||||
private_clans_1: "Private Clans provide increased privacy and detailed progress information for each student."
|
||||
private_clans_2: "Pour créer un Clan privé, veuillez vous référer à la boîte à cocher 'Faire un clan privé' pendant la création du"
|
||||
private_clans_3: "."
|
||||
who_for_title: "A qui CodeCombat est t-il destiné ?"
|
||||
|
|
|
@ -262,7 +262,7 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
|
|||
victory_hour_of_code_done_yes: "はい、構いません"
|
||||
victory_experience_gained: "XP獲得"
|
||||
victory_gems_gained: "ジェム獲得"
|
||||
# victory_new_item: "New Item"
|
||||
victory_new_item: "ニューアイテム"
|
||||
# victory_viking_code_school: "Holy smokes, that was a hard level you just beat! If you aren't already a software developer, you should be. You just got fast-tracked for acceptance with Viking Code School, where you can take your skills to the next level and become a professional web developer in 14 weeks."
|
||||
# victory_become_a_viking: "Become a Viking"
|
||||
guide_title: "ガイド"
|
||||
|
@ -292,11 +292,11 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
|
|||
time_current: "今:"
|
||||
time_total: "最大:"
|
||||
time_goto: "行く:"
|
||||
# non_user_code_problem_title: "Unable to Load Level"
|
||||
# infinite_loop_title: "Infinite Loop Detected"
|
||||
# infinite_loop_description: "The initial code to build the world never finished running. It's probably either really slow or has an infinite loop. Or there might be a bug. You can either try running this code again or reset the code to the default state. If that doesn't fix it, please let us know."
|
||||
# check_dev_console: "You can also open the developer console to see what might be going wrong."
|
||||
# check_dev_console_link: "(instructions)"
|
||||
non_user_code_problem_title: "レベルをロードできません"
|
||||
infinite_loop_title: "無限ループが見つかりました"
|
||||
infinite_loop_description: "最初のワールドを作るコードが終わりません。単に遅いか、無限ループになっているかでしょう。バグがあるのかもしれません。再試行してみたり、リセットしてデフォルトに戻すこともできます。もし直せないなら私たちに報告してください。"
|
||||
check_dev_console: "開発者コンソールをみてなにが間違っているか見ることもできます。"
|
||||
check_dev_console_link: "(説明書)"
|
||||
infinite_loop_try_again: "再試行する"
|
||||
infinite_loop_reset_level: "レベルをリセット"
|
||||
infinite_loop_comment_out: "マイコードをコメントアウト"
|
||||
|
@ -345,8 +345,8 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
|
|||
tip_hate_computers: "コンピュータを憎む人が本当に嫌いなのは下手なプログラマーだ。- ラリー・ニーヴン"
|
||||
tip_open_source_contribute: "あなたは CodeCombat をより良くすることができます!"
|
||||
tip_recurse: "繰り返しは人間、再帰は神。 - L・ピーター・ドイツ"
|
||||
# tip_free_your_mind: "You have to let it all go, Neo. Fear, doubt, and disbelief. Free your mind. - Morpheus"
|
||||
# tip_strong_opponents: "Even the strongest of opponents always has a weakness. - Itachi Uchiha"
|
||||
tip_free_your_mind: "全ての雑念を捨てろ、恐怖、疑いも不信も 心を解き放つんだ - モーフィアス"
|
||||
tip_strong_opponents: "どんな強者にも弱点というものはあるんだ… - うちは イタチ"
|
||||
|
||||
game_menu:
|
||||
inventory_tab: "インベントリー"
|
||||
|
@ -405,24 +405,24 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
|
|||
recovered: "前のジェム購入をリカバリーしました。ページを更新してください。"
|
||||
price: "x3500 / 月"
|
||||
|
||||
# subscribe:
|
||||
# comparison_blurb: "Sharpen your skills with a CodeCombat subscription!"
|
||||
# feature1: "80+ basic levels across 4 worlds"
|
||||
# feature2: "7 powerful <strong>new heroes</strong> with unique skills!"
|
||||
# feature3: "60+ bonus levels"
|
||||
# feature4: "<strong>3500 bonus gems</strong> every month!"
|
||||
# feature5: "Video tutorials"
|
||||
# feature6: "Premium email support"
|
||||
subscribe:
|
||||
comparison_blurb: "CodeCombatへ課金してスキルを磨きましょう!"
|
||||
feature1: "80以上の基本レベルが4つの世界に"
|
||||
feature2: "7人のパワフルな <strong>ニューヒーロー</strong> とユニークなスキル!"
|
||||
feature3: "60以上のボーナスレベル"
|
||||
feature4: "<strong>3500のジェム</strong>が毎月ボーナス!"
|
||||
feature5: "ビデオチュートリアル"
|
||||
feature6: "プレミアムメールサポート"
|
||||
# feature7: "Private <strong>Clans</strong>"
|
||||
# free: "Free"
|
||||
# month: "month"
|
||||
# subscribe_title: "Subscribe"
|
||||
# unsubscribe: "Unsubscribe"
|
||||
free: "無料"
|
||||
month: "月"
|
||||
subscribe_title: "課金"
|
||||
unsubscribe: "無課金"
|
||||
# confirm_unsubscribe: "Confirm Unsubscribe"
|
||||
# never_mind: "Never Mind, I Still Love You"
|
||||
# thank_you_months_prefix: "Thank you for supporting us these last"
|
||||
# thank_you_months_suffix: "months."
|
||||
# thank_you: "Thank you for supporting CodeCombat."
|
||||
never_mind: "気にしないでください, それでもあなたが好きです"
|
||||
thank_you_months_prefix: "私達を "
|
||||
thank_you_months_suffix: "ヶ月サポートしてくださりありがとうございます。"
|
||||
thank_you: "CodeCombatをサポートして下さりありがとうございます。"
|
||||
# sorry_to_see_you_go: "Sorry to see you go! Please let us know what we could have done better."
|
||||
# unsubscribe_feedback_placeholder: "O, what have we done?"
|
||||
# parent_button: "Ask your parent"
|
||||
|
@ -603,8 +603,8 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
|
|||
teacher_subs_2: "に連絡して無料の月々のサブスクリプションを得ましょう。" # {change}
|
||||
# teacher_subs_3: "to set up your subscription."
|
||||
sub_includes_title: "サブスクリプションの内容について"
|
||||
sub_includes_1: "70以上の基本レベルに加えて、生徒は月々のサブスクリプションを得て次の機能が使えます:" # {change}
|
||||
sub_includes_2: "40以上の練習レベル" # {change}
|
||||
sub_includes_1: "80以上の基本レベルに加えて、生徒は月々のサブスクリプションを得て次の機能が使えます:"
|
||||
sub_includes_2: "60以上の練習レベル"
|
||||
sub_includes_3: "ビデオチュートリアル"
|
||||
sub_includes_4: "メールによるサポート"
|
||||
sub_includes_5: "7人の新しいヒーローとマスターのユニークなスキル"
|
||||
|
@ -848,23 +848,23 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
|
|||
poll_title: "投票エディター"
|
||||
back: "バック"
|
||||
revert: "戻す"
|
||||
# revert_models: "Revert Models"
|
||||
revert_models: "モデルを戻す"
|
||||
pick_a_terrain: "地形を選択してください"
|
||||
dungeon: "ダンジョン"
|
||||
indoor: "屋内"
|
||||
desert: "砂漠"
|
||||
grassy: "草原"
|
||||
# small: "Small"
|
||||
# large: "Large"
|
||||
# fork_title: "Fork New Version"
|
||||
# fork_creating: "Creating Fork..."
|
||||
# generate_terrain: "Generate Terrain"
|
||||
# more: "More"
|
||||
small: "小さい"
|
||||
large: "大きい"
|
||||
fork_title: "新しいバージョンをフォークする"
|
||||
fork_creating: "フォークを作成中"
|
||||
generate_terrain: "地形を生成"
|
||||
more: "さらに見る"
|
||||
wiki: "ウィキ"
|
||||
live_chat: "ライブチャット"
|
||||
# thang_main: "Main"
|
||||
# thang_spritesheets: "Spritesheets"
|
||||
# thang_colors: "Colors"
|
||||
thang_main: "メイン"
|
||||
thang_spritesheets: "スプライトシート"
|
||||
thang_colors: "色"
|
||||
# level_some_options: "Some Options?"
|
||||
# level_tab_thangs: "Thangs"
|
||||
# level_tab_scripts: "Scripts"
|
||||
|
|
|
@ -440,9 +440,9 @@ module.exports = nativeDescription: "Українська", englishDescription:
|
|||
parents_blurb2: "За 9.99$ на місяць, вона отримуватиме нові завдання щотижня та персональні листи підтримки від професійних програмістів." # {change}
|
||||
parents_blurb3: "Жодного ризику: 100% гарантія повернення грошей, легке скасування абонементу одним кліком."
|
||||
payment_methods: "Платіжні методи"
|
||||
# payment_methods_title: "Accepted Payment Methods"
|
||||
payment_methods_title: "Платіжні методи, що приймаються"
|
||||
payment_methods_blurb1: "Наразі ми приймаємо кредитні картник та Alpiay."
|
||||
# payment_methods_blurb2: "If you require an alternate form of payment, please contact"
|
||||
payment_methods_blurb2: "Якщо Вам необхідно використати інший спосіб оплати, будь ласка, зв'яжіться з нами."
|
||||
stripe_description: "Щомісячний абонемент"
|
||||
subscription_required_to_play: "Аби грати в цьому рівні потрібен абонемент."
|
||||
unlock_help_videos: "Підпишіться, щоб відкрити усі навчальні відео."
|
||||
|
@ -455,7 +455,7 @@ module.exports = nativeDescription: "Українська", englishDescription:
|
|||
was_free_until: "У Вас був безкоштовний абонемент до "
|
||||
managed_subs: "Керовані абонементи"
|
||||
managed_subs_desc: "Додати абонементи для інших гравців (учнів, дітей тощо)"
|
||||
# managed_subs_desc_2: "Recipients must have a CodeCombat account associated with the email address you provide."
|
||||
managed_subs_desc_2: "Одержувачі повинні мати обліковий запис CodeCombat, пов'язаний з вказаною Вами адресою електронної пошти."
|
||||
group_discounts: "Групові знижки"
|
||||
group_discounts_1: "Ми також пропонуємо знижки для пакетних передплат."
|
||||
group_discounts_1st: "1-ий абонемент (включає Ваш)" # {change}
|
||||
|
@ -592,8 +592,8 @@ module.exports = nativeDescription: "Українська", englishDescription:
|
|||
|
||||
teachers:
|
||||
title: "CodeCombat для вчителів" # {change}
|
||||
# intro_1: "CodeCombat is an online game that teaches programming. Students write code in real programming languages."
|
||||
# intro_2: "No experience required!"
|
||||
intro_1: "CodeCombat - це онлайн гра, що вчить програмуванню. Студенти пишуть код на реальних мовах програмування."
|
||||
intro_2: "Досвід не потрібен!"
|
||||
free_title: "Скільки це коштує?"
|
||||
# cost_china: "CodeCombat in China is free for the first five levels, after which it costs $9.99 USD per month for access to our other 140+ levels on our exclusive China servers."
|
||||
# free_1: "There are 80+ FREE levels which cover every concept."
|
||||
|
@ -626,17 +626,17 @@ module.exports = nativeDescription: "Українська", englishDescription:
|
|||
# material_china: "Approximately 30 hours of gameplay spread over 140+ subscriber-only levels so far, with new levels every week."
|
||||
# material_1: "Approximately 10 hours of free content and an additional 20 hours of subscriber content, with new levels every week."
|
||||
# concepts_title: "What concepts are covered?"
|
||||
# how_much_title: "How much does a monthly subscription cost?"
|
||||
# how_much_1: "A"
|
||||
# how_much_2: "monthly subscription"
|
||||
# how_much_3: "costs $9.99, and can be cancelled anytime."
|
||||
# how_much_4: "Additionally, we provide discounts for larger groups:"
|
||||
# how_much_5: "We accept discounted one-time purchases and yearly subscription purchases for groups, such as a class or school. Please contact"
|
||||
# how_much_6: "for more details."
|
||||
# more_info_title: "Where can I find more information?"
|
||||
# more_info_1: "Our"
|
||||
# more_info_2: "teachers forum"
|
||||
# more_info_3: "is a good place to connect with fellow educators who are using CodeCombat."
|
||||
how_much_title: "Скільки коштує місячна передплата?"
|
||||
how_much_1: ""
|
||||
how_much_2: "Місячна передплата"
|
||||
how_much_3: "коштує $9.99, та може бути скасована будь-коли."
|
||||
how_much_4: "Крім цього, ми надаємо знижки для великих груп:"
|
||||
how_much_5: "Ми надаємо знижку на разові закупівлі та річну передплату для груп, таких як клас або школа. Будь ласка, зв'яжіться з нами"
|
||||
how_much_6: "для отримання більш детальної інформації."
|
||||
more_info_title: "Де я можу знайти більше інформації?"
|
||||
more_info_1: "Наш"
|
||||
more_info_2: "вчительський форум"
|
||||
more_info_3: "є гарним місцем для спілкування із колегами-педагогами, котрі використовують CodeCombat."
|
||||
sys_requirements_title: "Системні вимоги"
|
||||
sys_requirements_1: "Оскільки CodeCombat — це гра, для нормальної роботи він вимагає у комп'ютерів більше, ніж відео чи текстові посібники. Ми оптимізували його для швидкої роботи в усіх сучасних браузерах і на старіших машинах, щоб кожен міг грати. І ось наші підказки, як отримати від CodeCombat якнайбільше:" # {change}
|
||||
sys_requirements_2: "Використовуйте новіші версії Chrome або Firefox." # {change}
|
||||
|
@ -782,27 +782,27 @@ module.exports = nativeDescription: "Українська", englishDescription:
|
|||
new_name: "Назва нового клану"
|
||||
new_description: "Опис нового клану"
|
||||
make_private: "Зробити клан приватним"
|
||||
# subs_only: "subscribers only"
|
||||
subs_only: "лише для підписчиків"
|
||||
create_clan: "Створити новий клан"
|
||||
public_clans: "Публічні клани"
|
||||
my_clans: "Мої клани"
|
||||
clan_name: "Назва клану"
|
||||
# name: "Name"
|
||||
# chieftain: "Chieftain"
|
||||
name: "Ім'я"
|
||||
chieftain: "Отаман"
|
||||
type: "Тип"
|
||||
edit_clan_name: "Змінити назву клану"
|
||||
edit_clan_description: "Змінити опис клану"
|
||||
# edit_name: "edit name"
|
||||
# edit_description: "edit description"
|
||||
# private: "(private)"
|
||||
edit_name: "Змінити ім'я"
|
||||
edit_description: "Змінити опис"
|
||||
private: "(закритий)"
|
||||
summary: "Загалом"
|
||||
average_level: "Середній рівень"
|
||||
# average_achievements: "Average Achievements"
|
||||
delete_clan: "Видалити калн"
|
||||
average_achievements: "Середні досягнення"
|
||||
delete_clan: "Видалити клан"
|
||||
leave_clan: "Покинути клан"
|
||||
join_clan: "Приєднатись до клану"
|
||||
invite_1: "Запрошення:"
|
||||
invite_2: "*Запросіть гравців до цього Клану виславши дане посилання."
|
||||
invite_2: "*Запросіть гравців до цього Клану, виславши дане посилання."
|
||||
members: "Учасники"
|
||||
progress: "Поступ"
|
||||
not_started_1: "не розпочато"
|
||||
|
|
|
@ -47,7 +47,20 @@ block content
|
|||
a(href="https://s3.amazonaws.com/CodeCombatMisc/press_packet.zip", data-i18n="about.press_paragraph_1_link") press packet
|
||||
span(data-i18n="about.press_paragraph_1_suffix")
|
||||
| . All logos and images may be used without contacting us directly.
|
||||
|
||||
|
||||
h2(data-i18n="about.contact_title")
|
||||
| Contact
|
||||
p
|
||||
span(data-i18n="about.codecombat_inc")
|
||||
| CodeCombat, Inc.
|
||||
br
|
||||
span(data-i18n="about.address_part_1")
|
||||
| 188 King St #507
|
||||
br
|
||||
span(data-i18n="about.address_part_2")
|
||||
| San Francisco, CA 94107
|
||||
br
|
||||
a(href='mailto:team@codecombat.com') team@codecombat.com
|
||||
|
||||
ul.col-sm-6.team-column
|
||||
|
||||
|
@ -79,9 +92,11 @@ block content
|
|||
|
||||
li.row
|
||||
|
||||
img(src="/images/pages/about/george_small.png").img-thumbnail
|
||||
a(href="http://www.georgesaines.com/")
|
||||
img(src="/images/pages/about/george_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name George Saines
|
||||
h4.team_name
|
||||
a(href="http://www.georgesaines.com/") George Saines
|
||||
p(data-i18n="about.george_title")
|
||||
| Cofounder
|
||||
p(data-i18n="about.george_blurb")
|
||||
|
@ -130,7 +145,7 @@ block content
|
|||
| Floor Is Lava
|
||||
|
||||
a(href="https://soundcloud.com/taking-off")
|
||||
img(src="/images/pages/about/placeholder.png").img-thumbnail
|
||||
img(src="/images/pages/about/jose_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name
|
||||
a(href="https://soundcloud.com/taking-off") Jose Antonini
|
||||
|
@ -142,7 +157,7 @@ block content
|
|||
li.row
|
||||
|
||||
a(href="http://retrostylegames.com/")
|
||||
img(src="/images/pages/about/placeholder.png").img-thumbnail
|
||||
img(src="/images/pages/about/pavel_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name
|
||||
a(href="http://retrostylegames.com/") Pavel Konstantinov
|
||||
|
@ -152,7 +167,7 @@ block content
|
|||
| RetroStyle Games
|
||||
|
||||
a(href="http://retrostylegames.com/")
|
||||
img(src="/images/pages/about/placeholder.png").img-thumbnail
|
||||
img(src="/images/pages/about/oleg_small.png").img-thumbnail
|
||||
.team_bio
|
||||
h4.team_name
|
||||
a(href="http://retrostylegames.com/") Oleg Ulyanickiy
|
||||
|
|
|
@ -73,7 +73,7 @@ block append content
|
|||
tr
|
||||
td
|
||||
a(href="/clans/#{clan.id}")= clan.get('name')
|
||||
td
|
||||
td
|
||||
if idNameMap && idNameMap[clan.get('ownerID')]
|
||||
a(href="/user/#{clan.get('ownerID')}")= idNameMap[clan.get('ownerID')]
|
||||
else
|
||||
|
@ -160,7 +160,7 @@ block append content
|
|||
each achievement, index in earnedAchievements.models
|
||||
tr(class=index > 4 ? 'hide' : '')
|
||||
td= achievement.get('achievementName')
|
||||
td= moment().format("MMMM Do YYYY", achievement.get('changed'))
|
||||
td= moment(achievement.get('changed')).format("MMMM Do YYYY")
|
||||
if achievement.get('achievedAmount')
|
||||
td= achievement.get('achievedAmount')
|
||||
else
|
||||
|
|
|
@ -306,7 +306,7 @@ class RecipientSubs
|
|||
|
||||
options = { cache: false, url: "/db/user/#{me.id}/stripe" }
|
||||
options.success = (info) =>
|
||||
@sponsorSub = info.subscription
|
||||
@sponsorSub = info.sponsorSubscription
|
||||
if card = info.card
|
||||
@card = "#{card.brand}: x#{card.last4}"
|
||||
render()
|
||||
|
|
|
@ -121,7 +121,7 @@ module.exports = class DiplomatView extends ContributeClassView
|
|||
vi: ['An Nguyen Hoang Thien'] # Tiếng Việt, Vietnamese
|
||||
hu: ['Anon', 'atlantisguru', 'bbeasmile', 'csuvsaregal', 'divaDseidnA', 'ferpeter', 'kinez'] # magyar, Hungarian
|
||||
th: ['Kamolchanok Jittrepit'] # ไทย, Thai
|
||||
da: ['Anon', 'Einar Rasmussen', 'Rahazan', 'Randi Hillerøe', 'Silwing', 'marc-portier', 'sorsjen'] # dansk, Danish
|
||||
da: ['Anon', 'Einar Rasmussen', 'Rahazan', 'Randi Hillerøe', 'Silwing', 'marc-portier', 'sorsjen', 'Zleep-Dogg'] # dansk, Danish
|
||||
ko: ['Melondonut'] # 한국어, Korean
|
||||
sk: ['Anon', 'Juraj Pecháč'] # slovenčina, Slovak
|
||||
sl: [] # slovenščina, Slovene
|
||||
|
|
|
@ -70,6 +70,7 @@ defaultTasks =
|
|||
'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.'
|
||||
'Add to Existence System power table.'
|
||||
]
|
||||
Hero: commonTasks.concat animatedThangTypeTasks.concat purchasableTasks.concat [
|
||||
'Set the hero class.'
|
||||
|
|
|
@ -265,7 +265,6 @@ module.exports = class CampaignView extends RootView
|
|||
level.locked = false if @editorMode
|
||||
level.locked = false if @campaign?.get('name') is 'Auditions'
|
||||
level.locked = false if me.isInGodMode()
|
||||
level.locked = false if level.slug is 'apocalypse'
|
||||
level.disabled = true if level.adminOnly and @levelStatusMap[level.slug] not in ['started', 'complete']
|
||||
level.disabled = false if me.isInGodMode()
|
||||
level.color = 'rgb(255, 80, 60)'
|
||||
|
@ -306,7 +305,7 @@ module.exports = class CampaignView extends RootView
|
|||
if nextLevel and not nextLevel.locked and not nextLevel.disabled and @levelStatusMap[nextLevel.slug] isnt 'complete' and nextLevel.slug not in dontPointTo and not nextLevel.replayable and (
|
||||
me.isPremium() or
|
||||
not nextLevel.requiresSubscription or
|
||||
nextLevel.slug is 'apocalypse' or
|
||||
(nextLevel.slug is 'boom-and-bust' and not @levelStatusMap['defense-of-plainswood']) or
|
||||
(nextLevel.slug is 'favorable-odds' and not @levelStatusMap['the-raised-sword'])
|
||||
)
|
||||
nextLevel.next = true
|
||||
|
@ -352,9 +351,9 @@ module.exports = class CampaignView extends RootView
|
|||
particleKey.push level.type if level.type and level.type isnt 'hero'
|
||||
particleKey.push 'replayable' if level.replayable
|
||||
particleKey.push 'premium' if level.requiresSubscription
|
||||
particleKey.push 'gate' if level.slug in ['kithgard-gates', 'siege-of-stonehold', 'clash-of-clones']
|
||||
particleKey.push 'gate' if level.slug in ['kithgard-gates', 'siege-of-stonehold', 'clash-of-clones', 'summits-gate']
|
||||
particleKey.push 'hero' if level.unlocksHero and not level.unlockedHero
|
||||
particleKey.push 'item' if level.slug is 'apocalypse' # TODO: generalize
|
||||
#particleKey.push 'item' if level.slug is 'apocalypse' # TODO: generalize
|
||||
continue if particleKey.length is 2 # Don't show basic levels
|
||||
continue unless level.hidden or _.intersection(particleKey, ['item', 'hero-ladder', 'replayable']).length
|
||||
@particleMan.addEmitter level.position.x / 100, level.position.y / 100, particleKey.join('-')
|
||||
|
|
|
@ -329,7 +329,7 @@ module.exports = class HeroVictoryModal extends ModalView
|
|||
AudioPlayer.playSound name, 1
|
||||
|
||||
getNextLevelCampaign: ->
|
||||
{'kithgard-gates': 'forest', 'siege-of-stonehold': 'desert', 'clash-of-clones': 'mountain'}[@level.get('slug')] or @level.get 'campaign' # Much easier to just keep this updated than to dynamically figure it out.
|
||||
{'kithgard-gates': 'forest', 'kithgard-mastery': 'forest', 'siege-of-stonehold': 'desert', 'clash-of-clones': 'mountain'}[@level.get('slug')] or @level.get 'campaign' # Much easier to just keep this updated than to dynamically figure it out.
|
||||
|
||||
getNextLevelLink: ->
|
||||
link = '/play'
|
||||
|
|
|
@ -616,7 +616,7 @@ module.exports = class InventoryModal extends ModalView
|
|||
for slot, original of equipment
|
||||
item = _.find @items.models, (item) -> item.get('original') is original
|
||||
continue unless dollImages = item?.get('dollImages')
|
||||
didAdd = @addDollImage slot, dollImages, heroClass, gender
|
||||
didAdd = @addDollImage slot, dollImages, heroClass, gender, item
|
||||
slotsWithImages.push slot if didAdd if item.get('original') isnt '54ea39342b7506e891ca70f2' # Circlet of the Magi needs hair under it
|
||||
@$el.find('#hero-image-hair').toggle not ('head' in slotsWithImages)
|
||||
@$el.find('#hero-image-thumb').toggle not ('gloves' in slotsWithImages)
|
||||
|
@ -626,7 +626,7 @@ module.exports = class InventoryModal extends ModalView
|
|||
removeDollImages: ->
|
||||
@$el.find('.doll-image').remove()
|
||||
|
||||
addDollImage: (slot, dollImages, heroClass, gender) ->
|
||||
addDollImage: (slot, dollImages, heroClass, gender, item) ->
|
||||
heroClass = @selectedHero?.get('heroClass') ? 'Warrior'
|
||||
gender = if @selectedHero?.get('slug') in heroGenders.male then 'male' else 'female'
|
||||
didAdd = false
|
||||
|
@ -637,6 +637,9 @@ module.exports = class InventoryModal extends ModalView
|
|||
imageKeys = ["#{gender}", "#{gender}Thumb"]
|
||||
else if heroClass is 'Wizard' and slot is 'torso'
|
||||
imageKeys = [gender, "#{gender}Back"]
|
||||
else if heroClass is 'Ranger' and slot is 'head' and item.get('original') in ['5441c2be4e9aeb727cc97105', '5441c3144e9aeb727cc97111']
|
||||
# All-class headgear like faux fur hat, viking helmet is abusing ranger glove slot
|
||||
imageKeys = ["#{gender}Ranger"]
|
||||
else
|
||||
imageKeys = [gender]
|
||||
for imageKey in imageKeys
|
||||
|
|
136
scripts/analytics/mongodb/queries/averageLevelPlaytimes.js
Normal file
136
scripts/analytics/mongodb/queries/averageLevelPlaytimes.js
Normal file
|
@ -0,0 +1,136 @@
|
|||
// Average level playtimes by campaign
|
||||
|
||||
// Usage:
|
||||
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
||||
|
||||
// NOTE: faster to use find() instead of aggregate()
|
||||
// NOTE: faster to ask for one level at a time. also keeps levels in campaign order
|
||||
|
||||
// Excluded for one reason or another
|
||||
// Some relevant code: https://github.com/codecombat/codecombat/blob/master/app/views/play/CampaignView.coffee#L281-L292
|
||||
var excludedLevels = ['deadly-dungeon-rescue', 'kithgard-brawl', 'cavern-survival', 'kithgard-mastery', 'destroying-angel', 'kithgard-apprentice', 'wild-horses', 'lost-viking', 'forest-flower-grove', 'boulder-woods', 'the-trials'];
|
||||
|
||||
var scriptStartTime = new Date();
|
||||
var startDay = '2015-05-10';
|
||||
var endDay = '2015-06-11';
|
||||
|
||||
log("Dates: " + startDay + " to " + endDay);
|
||||
|
||||
// Print out playtimes for each campaign
|
||||
var campaigns = getCampaigns();
|
||||
for (var i = 0; i < campaigns.length; i++) {
|
||||
var campaign = campaigns[i];
|
||||
// if (campaign.slug !== 'dungeon') continue;
|
||||
print(campaign.slug + " (free)");
|
||||
var total = 0;
|
||||
|
||||
for (var j = 0; j < campaign.free.length; j++) {
|
||||
var levelSlug = campaign.free[j];
|
||||
if (excludedLevels.indexOf(levelSlug) >= 0) continue;
|
||||
var data = getPlaytimes([levelSlug]);
|
||||
print(data[levelSlug].average + "\t" + data[levelSlug].count + "\t" + levelSlug);
|
||||
total += data[levelSlug];
|
||||
}
|
||||
// print(parseInt(total/60/60) + "\t\t total hours");
|
||||
total = 0;
|
||||
|
||||
print(campaign.slug + " (paid)");
|
||||
for (var j = 0; j < campaign.paid.length; j++) {
|
||||
var levelSlug = campaign.paid[j];
|
||||
if (excludedLevels.indexOf(levelSlug) >= 0) continue;
|
||||
var data = getPlaytimes([levelSlug]);
|
||||
if (data[levelSlug]) {
|
||||
print(data[levelSlug].average + "\t" + data[levelSlug].count + "\t" + levelSlug);
|
||||
total += data[levelSlug];
|
||||
}
|
||||
else {
|
||||
print("0\t0\t" + levelSlug);
|
||||
}
|
||||
}
|
||||
// print(parseInt(total/60/60) + "\t\t total hours");
|
||||
total = 0;
|
||||
|
||||
print(campaign.slug + " (replayable)");
|
||||
for (var j = 0; j < campaign.replayable.length; j++) {
|
||||
var levelSlug = campaign.replayable[j];
|
||||
if (excludedLevels.indexOf(levelSlug) >= 0) continue;
|
||||
var data = getPlaytimes([levelSlug]);
|
||||
print(data[levelSlug].average + "\t" + data[levelSlug].count + "\t" + levelSlug);
|
||||
total += data[levelSlug];
|
||||
}
|
||||
// print(parseInt(total/60/60) + "\t\t total hours");
|
||||
|
||||
// break;
|
||||
}
|
||||
|
||||
log("Script runtime: " + (new Date() - scriptStartTime));
|
||||
|
||||
function log(str) {
|
||||
print(new Date().toISOString() + " " + str);
|
||||
}
|
||||
|
||||
function objectIdWithTimestamp(timestamp) {
|
||||
// Convert string date to Date object (otherwise assume timestamp is a date)
|
||||
if (typeof(timestamp) == 'string') timestamp = new Date(timestamp);
|
||||
// Convert date object to hex seconds since Unix epoch
|
||||
var hexSeconds = Math.floor(timestamp/1000).toString(16);
|
||||
// Create an ObjectId with that hex timestamp
|
||||
var constructedObjectId = ObjectId(hexSeconds + "0000000000000000");
|
||||
return constructedObjectId
|
||||
}
|
||||
|
||||
function getCampaigns() {
|
||||
var campaigns = [];
|
||||
var cursor = db.campaigns.find({}, {slug: 1, levels: 1});
|
||||
var allFree = 0;
|
||||
var allpaid = 0;
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
if (doc.slug === 'auditions') continue;
|
||||
var campaign = {slug: doc.slug, free: [], paid: [], replayable: []};
|
||||
for (var levelID in doc.levels) {
|
||||
if (doc.levels[levelID].replayable) {
|
||||
campaign.replayable.push(doc.levels[levelID].slug);
|
||||
}
|
||||
else if (doc.levels[levelID].requiresSubscription) {
|
||||
campaign.paid.push(doc.levels[levelID].slug);
|
||||
}
|
||||
else {
|
||||
campaign.free.push(doc.levels[levelID].slug);
|
||||
}
|
||||
}
|
||||
campaigns.push(campaign);
|
||||
}
|
||||
return campaigns;
|
||||
}
|
||||
|
||||
function getPlaytimes(levelSlugs) {
|
||||
var startObj = objectIdWithTimestamp(ISODate(startDay + "T00:00:00.000Z"));
|
||||
var endObj = objectIdWithTimestamp(ISODate(endDay + "T00:00:00.000Z"))
|
||||
var cursor = db['level.sessions'].find({
|
||||
$and:
|
||||
[
|
||||
{"state.complete": true},
|
||||
{"playtime": {$gt: 0}},
|
||||
{levelID: {$in: levelSlugs}},
|
||||
{_id: {$gte: startObj}},
|
||||
{_id: {$lt: endObj}}
|
||||
]
|
||||
});
|
||||
|
||||
var playtimes = {};
|
||||
while (cursor.hasNext()) {
|
||||
var myDoc = cursor.next();
|
||||
var levelID = myDoc.levelID;
|
||||
if (!playtimes[levelID]) playtimes[levelID] = [];
|
||||
playtimes[levelID].push(myDoc.playtime);
|
||||
}
|
||||
|
||||
var data = {};
|
||||
for (levelID in playtimes) {
|
||||
var total = playtimes[levelID].reduce(function(a, b) {return a + b;});
|
||||
data[levelID] = {count: playtimes[levelID].length, total: total};
|
||||
data[levelID]['average'] = parseInt(total / playtimes[levelID].length);
|
||||
}
|
||||
return data;
|
||||
}
|
153
scripts/analytics/mongodb/queries/levelCompletionCounts.js
Normal file
153
scripts/analytics/mongodb/queries/levelCompletionCounts.js
Normal file
|
@ -0,0 +1,153 @@
|
|||
// Level completion counts broken down into free and paid buckets
|
||||
|
||||
// Usage:
|
||||
// mongo <address>:<port>/<database> <script file> -u <username> -p <password>
|
||||
|
||||
// TODO: subscriber is someone who is currently subscribed, not necessarily subscribed when they completed a level
|
||||
|
||||
// Excluded for one reason or another
|
||||
// Some relevant code: https://github.com/codecombat/codecombat/blob/master/app/views/play/CampaignView.coffee#L281-L292
|
||||
var excludedLevels = ['deadly-dungeon-rescue', 'kithgard-brawl', 'cavern-survival', 'kithgard-mastery', 'destroying-angel', 'kithgard-apprentice', 'wild-horses', 'lost-viking', 'forest-flower-grove', 'boulder-woods', 'the-trials'];
|
||||
|
||||
var scriptStartTime = new Date();
|
||||
var startDay = '2015-05-16';
|
||||
var endDay = '2015-06-17';
|
||||
|
||||
log("Dates: " + startDay + " to " + endDay);
|
||||
|
||||
var subscribers = getSubscribers();
|
||||
log("Subscriber count: " + Object.keys(subscribers).length);
|
||||
|
||||
var campaigns = getCampaigns();
|
||||
for (var i = 0; i < campaigns.length; i++) {
|
||||
var campaign = campaigns[i];
|
||||
// if (campaign.slug !== 'mountain') continue;
|
||||
|
||||
function printCampaign(title, prop) {
|
||||
print(title)
|
||||
print("Total\tFree\tSubscribers");
|
||||
for (var j = 0; j < campaign[prop].length; j++) {
|
||||
var levelSlug = campaign[prop][j];
|
||||
if (excludedLevels.indexOf(levelSlug) >= 0) continue;
|
||||
var data = getCompletionCounts([levelSlug], subscribers);
|
||||
if (data[levelSlug]) {
|
||||
var free = data[levelSlug].free.length;
|
||||
var paid = data[levelSlug].paid.length;
|
||||
var total = free + paid;
|
||||
var paidRate = parseInt(paid / total * 100);
|
||||
print(total + "\t" + free + "\t" + paid + "\t\t" + paidRate + "%\t" + levelSlug);
|
||||
}
|
||||
else {
|
||||
print("0\t0\t0\t\t0%\t" + levelSlug);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printCampaign(campaign.slug + " (free)", "free");
|
||||
printCampaign(campaign.slug + " (paid)", "paid");
|
||||
printCampaign(campaign.slug + " (replayable)", "replayable");
|
||||
|
||||
// break;
|
||||
}
|
||||
|
||||
log("Script runtime: " + (new Date() - scriptStartTime));
|
||||
|
||||
function log(str) {
|
||||
print(new Date().toISOString() + " " + str);
|
||||
}
|
||||
|
||||
function objectIdWithTimestamp(timestamp) {
|
||||
// Convert string date to Date object (otherwise assume timestamp is a date)
|
||||
if (typeof(timestamp) == 'string') timestamp = new Date(timestamp);
|
||||
// Convert date object to hex seconds since Unix epoch
|
||||
var hexSeconds = Math.floor(timestamp/1000).toString(16);
|
||||
// Create an ObjectId with that hex timestamp
|
||||
var constructedObjectId = ObjectId(hexSeconds + "0000000000000000");
|
||||
return constructedObjectId
|
||||
}
|
||||
|
||||
function getCampaigns() {
|
||||
var campaigns = [];
|
||||
var cursor = db.campaigns.find({}, {slug: 1, levels: 1});
|
||||
var allFree = 0;
|
||||
var allpaid = 0;
|
||||
while (cursor.hasNext()) {
|
||||
var doc = cursor.next();
|
||||
if (doc.slug === 'auditions') continue;
|
||||
var campaign = {slug: doc.slug, free: [], paid: [], replayable: []};
|
||||
for (var levelID in doc.levels) {
|
||||
if (doc.levels[levelID].replayable) {
|
||||
campaign.replayable.push(doc.levels[levelID].slug);
|
||||
}
|
||||
else if (doc.levels[levelID].requiresSubscription) {
|
||||
campaign.paid.push(doc.levels[levelID].slug);
|
||||
}
|
||||
else {
|
||||
campaign.free.push(doc.levels[levelID].slug);
|
||||
}
|
||||
}
|
||||
campaigns.push(campaign);
|
||||
}
|
||||
return campaigns;
|
||||
}
|
||||
|
||||
function getCompletionCounts(levelSlugs, subscribers) {
|
||||
var startObj = objectIdWithTimestamp(ISODate(startDay + "T00:00:00.000Z"));
|
||||
var endObj = objectIdWithTimestamp(ISODate(endDay + "T00:00:00.000Z"))
|
||||
var cursor = db['level.sessions'].find({
|
||||
$and:
|
||||
[
|
||||
{"state.complete": true},
|
||||
{levelID: {$in: levelSlugs}},
|
||||
{_id: {$gte: startObj}},
|
||||
{_id: {$lt: endObj}}
|
||||
]
|
||||
});
|
||||
|
||||
var completionCounts = {};
|
||||
while (cursor.hasNext()) {
|
||||
var myDoc = cursor.next();
|
||||
var userID = myDoc.creator;
|
||||
var levelID = myDoc.levelID;
|
||||
|
||||
if (!completionCounts[levelID]) completionCounts[levelID] = {free: [], paid: []};
|
||||
if (subscribers[userID]) {
|
||||
completionCounts[levelID].paid.push(myDoc._id.valueOf());
|
||||
}
|
||||
else {
|
||||
completionCounts[levelID].free.push(myDoc._id.valueOf());
|
||||
}
|
||||
}
|
||||
|
||||
return completionCounts;
|
||||
}
|
||||
|
||||
function getSubscribers() {
|
||||
var cursor = db['users'].find({
|
||||
$and:
|
||||
[
|
||||
{
|
||||
$or:
|
||||
[
|
||||
{"stripe.sponsorID": {$exists: true}},
|
||||
{$and:
|
||||
[
|
||||
{"stripe.subscriptionID": {$exists: true}},
|
||||
{"stripe.planID": 'basic'}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{permissions: {$ne: ['admin']}},
|
||||
{"stripe.free": {$exists: false}},
|
||||
{"stripe.coupon": {$exists: false}},
|
||||
{"stripe.prepaidCode": {$exists: false}}
|
||||
]
|
||||
});
|
||||
|
||||
var subscribers = {};
|
||||
while (cursor.hasNext()) {
|
||||
subscribers[cursor.next()._id.valueOf()] = true;
|
||||
}
|
||||
return subscribers;
|
||||
}
|
|
@ -23,5 +23,8 @@ LevelSystemHandler = class LevelSystemHandler extends Handler
|
|||
hasAccess: (req) ->
|
||||
req.method is 'GET' or req.user?.isAdmin() or req.user?.isArtisan()
|
||||
|
||||
hasAccessToDocument: (req, document, method) ->
|
||||
if req.user?.isArtisan() then true else super req, document, method
|
||||
|
||||
|
||||
module.exports = new LevelSystemHandler()
|
||||
|
|
|
@ -582,18 +582,26 @@ class SubscriptionHandler extends Handler
|
|||
email = req.body.stripe.unsubscribeEmail.trim().toLowerCase()
|
||||
return done({res: 'Database error.', code: 500}) if _.isEmpty(email)
|
||||
|
||||
deleteUserStripeProp = (user, propName) ->
|
||||
stripeInfo = _.cloneDeep(user.get('stripe') ? {})
|
||||
delete stripeInfo[propName]
|
||||
if _.isEmpty stripeInfo
|
||||
user.set 'stripe', undefined
|
||||
else
|
||||
user.set 'stripe', stripeInfo
|
||||
|
||||
User.findOne {emailLower: email}, (err, recipient) =>
|
||||
if err
|
||||
@logSubscriptionError(user, "User lookup error. " + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
unless recipient
|
||||
@logSubscriptionError(user, "Recipient #{req.body.stripe.recipient} not found. " + err)
|
||||
@logSubscriptionError(user, "Recipient #{email} not found.")
|
||||
return done({res: 'Database error.', code: 500})
|
||||
|
||||
# Check recipient is currently sponsored
|
||||
stripeRecipient = recipient.get 'stripe' ? {}
|
||||
if stripeRecipient?.sponsorID isnt user.id
|
||||
@logSubscriptionError(user, "Recipient #{req.body.stripe.recipient} not found. " + err)
|
||||
@logSubscriptionError(user, "Recipient #{req.body.stripe.recipient} not found. ")
|
||||
return done({res: 'Can only unsubscribe sponsored subscriptions.', code: 403})
|
||||
|
||||
# Find recipient subscription
|
||||
|
@ -603,22 +611,41 @@ class SubscriptionHandler extends Handler
|
|||
sponsoredEntry = sponsored
|
||||
break
|
||||
unless sponsoredEntry?
|
||||
@logSubscriptionError(user, 'Unable to find sponsored subscription. ' + err)
|
||||
@logSubscriptionError(user, 'Unable to find recipient subscription. ')
|
||||
return done({res: 'Database error.', code: 500})
|
||||
|
||||
# Cancel Stripe subscription
|
||||
stripe.customers.cancelSubscription stripeInfo.customerID, sponsoredEntry.subscriptionID, { at_period_end: true }, (err) =>
|
||||
if err or not recipient
|
||||
@logSubscriptionError(user, "Stripe cancel sponsored subscription failed. " + err)
|
||||
# Update recipient user
|
||||
deleteUserStripeProp(recipient, 'sponsorID')
|
||||
recipient.save (err) =>
|
||||
if err
|
||||
@logSubscriptionError(user, 'Recipient user save unsubscribe error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
|
||||
delete stripeInfo.unsubscribeEmail
|
||||
user.set('stripe', stripeInfo)
|
||||
req.body.stripe = stripeInfo
|
||||
user.save (err) =>
|
||||
# Cancel Stripe subscription
|
||||
stripe.customers.cancelSubscription stripeInfo.customerID, sponsoredEntry.subscriptionID, (err) =>
|
||||
if err
|
||||
@logSubscriptionError(user, 'User save unsubscribe error. ' + err)
|
||||
@logSubscriptionError(user, "Stripe cancel sponsored subscription failed. " + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
done()
|
||||
|
||||
# Update sponsor user
|
||||
_.remove(stripeInfo.recipients, (s) -> s.userID is recipient.id)
|
||||
delete stripeInfo.unsubscribeEmail
|
||||
user.set('stripe', stripeInfo)
|
||||
req.body.stripe = stripeInfo
|
||||
user.save (err) =>
|
||||
if err
|
||||
@logSubscriptionError(user, 'Sponsor user save unsubscribe error. ' + err)
|
||||
return done({res: 'Database error.', code: 500})
|
||||
|
||||
return done() unless stripeInfo.sponsorSubscriptionID?
|
||||
|
||||
# Update sponsored subscription quantity
|
||||
options =
|
||||
quantity: getSponsoredSubsAmount(subscriptions.basic.amount, stripeInfo.recipients.length, stripeInfo.subscriptionID?)
|
||||
stripe.customers.updateSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, options, (err, subscription) =>
|
||||
if err
|
||||
logStripeWebhookError(err)
|
||||
return res.send(500, '')
|
||||
done()
|
||||
|
||||
module.exports = new SubscriptionHandler()
|
||||
|
|
|
@ -157,6 +157,10 @@ module.exports.setup = (app) ->
|
|||
unless recipient
|
||||
logStripeWebhookError("Recipient not found #{subscription.metadata.id}")
|
||||
return res.send(500, '')
|
||||
|
||||
# Recipient cancellations are immediate, no work to perform if recipient's sponsorID is already gone
|
||||
return res.send(200, '') unless recipient.get('stripe')?.sponsorID?
|
||||
|
||||
User.findById recipient.get('stripe').sponsorID, (err, sponsor) =>
|
||||
if err
|
||||
logStripeWebhookError(err)
|
||||
|
|
|
@ -259,6 +259,8 @@ describe 'Subscriptions', ->
|
|||
# console.log 'verifyNotSponsoring', sponsorID, recipientID
|
||||
User.findById sponsorID, (err, sponsor) ->
|
||||
expect(err).toBeNull()
|
||||
expect(sponsor).not.toBeNull()
|
||||
return done() unless sponsor
|
||||
stripeInfo = sponsor.get('stripe')
|
||||
return done() unless stripeInfo?.customerID?
|
||||
checkSubscriptions = (starting_after, done) ->
|
||||
|
@ -282,6 +284,7 @@ describe 'Subscriptions', ->
|
|||
User.findById sponsorUserID, (err, user) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user).not.toBeNull()
|
||||
return done() unless user
|
||||
sponsorStripe = user.get('stripe')
|
||||
sponsorCustomerID = sponsorStripe.customerID
|
||||
numSponsored = sponsorStripe.recipients?.length
|
||||
|
@ -443,7 +446,7 @@ describe 'Subscriptions', ->
|
|||
expect(err?).toEqual(false)
|
||||
done(updatedUser)
|
||||
|
||||
unsubscribeRecipient = (sponsor, recipient, immediately, done) ->
|
||||
unsubscribeRecipient = (sponsor, recipient, done) ->
|
||||
# console.log 'unsubscribeRecipient', sponsor.id, recipient.id
|
||||
stripeInfo = sponsor.get('stripe')
|
||||
customerID = stripeInfo.customerID
|
||||
|
@ -467,20 +470,7 @@ describe 'Subscriptions', ->
|
|||
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
expect(res.statusCode).toBe(200)
|
||||
|
||||
# Simulate subscription ending after cancellation
|
||||
return done() unless immediately
|
||||
|
||||
# Simulate subscription cancelling at period end
|
||||
stripe.customers.cancelSubscription customerID, subscriptionID, (err) ->
|
||||
expect(err).toBeNull()
|
||||
|
||||
# Simulate customer.subscription.deleted webhook event
|
||||
event = _.cloneDeep(customerSubscriptionDeletedSampleEvent)
|
||||
event.data.object = subscription
|
||||
request.post {uri: webhookURL, json: event}, (err, res, body) ->
|
||||
expect(err).toBeNull()
|
||||
done()
|
||||
done()
|
||||
|
||||
# Subscribe a bunch of recipients at once, used for bulk discount testing
|
||||
class SubbedRecipients
|
||||
|
@ -762,11 +752,11 @@ describe 'Subscriptions', ->
|
|||
expect(err).toBeNull()
|
||||
|
||||
User.findById user1.id, (err, user1) ->
|
||||
unsubscribeRecipient user1, user2, true, ->
|
||||
unsubscribeRecipient user1, user2, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user1.get('stripe').subscriptionID).toBeDefined()
|
||||
expect(user1.get('stripe').recipients).toBeUndefined()
|
||||
expect(_.isEmpty(user1.get('stripe').recipients)).toEqual(true)
|
||||
expect(user1.isPremium()).toEqual(true)
|
||||
User.findById user2.id, (err, user2) ->
|
||||
verifyNotSponsoring user1.id, user2.id, ->
|
||||
|
@ -781,7 +771,7 @@ describe 'Subscriptions', ->
|
|||
loginNewUser (user1) ->
|
||||
subscribeRecipients user1, [user2], token, (updatedUser) ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
unsubscribeRecipient user1, user2, true, ->
|
||||
unsubscribeRecipient user1, user2, ->
|
||||
verifyNotSponsoring user1.id, user2.id, ->
|
||||
verifyNotRecipient user2.id, done
|
||||
|
||||
|
@ -793,7 +783,7 @@ describe 'Subscriptions', ->
|
|||
loginNewUser (user1) ->
|
||||
subscribeRecipients user1, [user2], token, (updatedUser) ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
unsubscribeRecipient user1, user2, false, ->
|
||||
unsubscribeRecipient user1, user2, ->
|
||||
subscribeRecipients user1, [user2], null, (updatedUser) ->
|
||||
verifySponsorship user1.id, user2.id, done
|
||||
|
||||
|
@ -846,7 +836,7 @@ describe 'Subscriptions', ->
|
|||
expect(err).toBeNull()
|
||||
subscribeRecipients user1, [user2], null, (updatedUser) ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
unsubscribeRecipient user1, user2, true, ->
|
||||
unsubscribeRecipient user1, user2, ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
expect(err).toBeNull()
|
||||
expect(user1.get('stripe').subscriptionID).toBeDefined()
|
||||
|
@ -1138,7 +1128,7 @@ describe 'Subscriptions', ->
|
|||
User.findById user1.id, (err, user1) ->
|
||||
|
||||
# Unsubscribe recipient0
|
||||
unsubscribeRecipient user1, recipients.get(0), true, ->
|
||||
unsubscribeRecipient user1, recipients.get(0), ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(1)
|
||||
|
@ -1150,7 +1140,7 @@ describe 'Subscriptions', ->
|
|||
expect(subscription.quantity).toEqual(getSubscribedQuantity(1))
|
||||
|
||||
# Unsubscribe recipient1
|
||||
unsubscribeRecipient user1, recipients.get(1), true, ->
|
||||
unsubscribeRecipient user1, recipients.get(1), ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(0)
|
||||
|
@ -1186,7 +1176,7 @@ describe 'Subscriptions', ->
|
|||
User.findById user1.id, (err, user1) ->
|
||||
|
||||
# Unsubscribe first recipient
|
||||
unsubscribeRecipient user1, recipients.get(0), true, ->
|
||||
unsubscribeRecipient user1, recipients.get(0), ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 1)
|
||||
|
@ -1198,7 +1188,7 @@ describe 'Subscriptions', ->
|
|||
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 1))
|
||||
|
||||
# Unsubscribe second recipient
|
||||
unsubscribeRecipient user1, recipients.get(1), true, ->
|
||||
unsubscribeRecipient user1, recipients.get(1), ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 2)
|
||||
|
@ -1218,7 +1208,7 @@ describe 'Subscriptions', ->
|
|||
|
||||
# Unsubscribe third recipient
|
||||
verifySponsorship user1.id, recipients.get(2).id, ->
|
||||
unsubscribeRecipient user1, recipients.get(2), true, ->
|
||||
unsubscribeRecipient user1, recipients.get(2), ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 3)
|
||||
|
@ -1252,8 +1242,9 @@ describe 'Subscriptions', ->
|
|||
User.findById user1.id, (err, user1) ->
|
||||
|
||||
# Unsubscribe first recipient
|
||||
unsubscribeRecipient user1, recipients.get(0), true, ->
|
||||
unsubscribeRecipient user1, recipients.get(0), ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 1)
|
||||
verifyNotSponsoring user1.id, recipients.get(0).id, ->
|
||||
|
@ -1264,7 +1255,7 @@ describe 'Subscriptions', ->
|
|||
expect(subscription.quantity).toEqual(getUnsubscribedQuantity(recipientCount - 1))
|
||||
|
||||
# Unsubscribe last recipient
|
||||
unsubscribeRecipient user1, recipients.get(recipientCount - 1), true, ->
|
||||
unsubscribeRecipient user1, recipients.get(recipientCount - 1), ->
|
||||
User.findById user1.id, (err, user1) ->
|
||||
stripeInfo = user1.get('stripe')
|
||||
expect(stripeInfo.recipients.length).toEqual(recipientCount - 2)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue