From db2500776534e5c71d530c36d581e02bb695683f Mon Sep 17 00:00:00 2001 From: Esben Madsen <codecombat.com@minpingvin.dk> Date: Thu, 11 Jun 2015 00:35:56 +0200 Subject: [PATCH 01/18] A bit of Danish translation pt. 2 --- app/locale/da.coffee | 100 +++++++++++------------ app/views/contribute/DiplomatView.coffee | 2 +- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/app/locale/da.coffee b/app/locale/da.coffee index 3ac820d9e..f15a5f3a2 100644 --- a/app/locale/da.coffee +++ b/app/locale/da.coffee @@ -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" diff --git a/app/views/contribute/DiplomatView.coffee b/app/views/contribute/DiplomatView.coffee index 94a0f38b7..24fa7ef79 100644 --- a/app/views/contribute/DiplomatView.coffee +++ b/app/views/contribute/DiplomatView.coffee @@ -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 From fdf500cac197677238db5932f6227c21c2c104a3 Mon Sep 17 00:00:00 2001 From: Matt Lott <mattlott@live.com> Date: Thu, 11 Jun 2015 14:32:10 -0700 Subject: [PATCH 02/18] Average level playtimes script --- .../mongodb/queries/averageLevelPlaytimes.js | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 scripts/analytics/mongodb/queries/averageLevelPlaytimes.js diff --git a/scripts/analytics/mongodb/queries/averageLevelPlaytimes.js b/scripts/analytics/mongodb/queries/averageLevelPlaytimes.js new file mode 100644 index 000000000..0a7f18330 --- /dev/null +++ b/scripts/analytics/mongodb/queries/averageLevelPlaytimes.js @@ -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; +} From c806b8865e80e93433e9e1225e49c692536ec1e8 Mon Sep 17 00:00:00 2001 From: Kevin Avignon <kevin.o.avignon@gmail.com> Date: Sat, 13 Jun 2015 11:39:25 -0400 Subject: [PATCH 03/18] Made some changes into the french file --- app/locale/fr.coffee | 88 ++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/app/locale/fr.coffee b/app/locale/fr.coffee index 2f1173363..64ff640d1 100644 --- a/app/locale/fr.coffee +++ b/app/locale/fr.coffee @@ -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 !" @@ -608,14 +608,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 clan." private_clans_3: "." who_for_title: "A qui CodeCombat est t-il destiné ?" From e263866a3a5e4278e61e7a0d04c96780d2bfbb6c Mon Sep 17 00:00:00 2001 From: Kevin Avignon <kevin.o.avignon@gmail.com> Date: Sat, 13 Jun 2015 23:33:48 -0400 Subject: [PATCH 04/18] Made some update within the spanish file --- app/locale/es-ES.coffee | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/locale/es-ES.coffee b/app/locale/es-ES.coffee index 979570579..ac4d94951 100644 --- a/app/locale/es-ES.coffee +++ b/app/locale/es-ES.coffee @@ -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" From 3f3f44038e929440523853bbb8a06dfb67092c09 Mon Sep 17 00:00:00 2001 From: Ikuyadeu <ikuyadeu@yahoo.co.jp> Date: Sun, 14 Jun 2015 17:11:26 +0900 Subject: [PATCH 05/18] Update ja.coffee --- app/locale/ja.coffee | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/locale/ja.coffee b/app/locale/ja.coffee index 191c2aa84..501dc2282 100644 --- a/app/locale/ja.coffee +++ b/app/locale/ja.coffee @@ -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: "インベントリー" From 7d637a0a27a393cd6c399fbeb068e08e11faa2ec Mon Sep 17 00:00:00 2001 From: Nick Winter <livelily@gmail.com> Date: Sun, 14 Jun 2015 11:07:21 -0700 Subject: [PATCH 06/18] Added a couple levels' next-campaign links. --- app/views/play/CampaignView.coffee | 2 +- app/views/play/level/modal/HeroVictoryModal.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/play/CampaignView.coffee b/app/views/play/CampaignView.coffee index 54f4d35ca..d2629223b 100644 --- a/app/views/play/CampaignView.coffee +++ b/app/views/play/CampaignView.coffee @@ -352,7 +352,7 @@ 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 continue if particleKey.length is 2 # Don't show basic levels diff --git a/app/views/play/level/modal/HeroVictoryModal.coffee b/app/views/play/level/modal/HeroVictoryModal.coffee index c5a644044..086548fc4 100644 --- a/app/views/play/level/modal/HeroVictoryModal.coffee +++ b/app/views/play/level/modal/HeroVictoryModal.coffee @@ -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' From f7eaf91bae1c893d062d130662771347f60555fd Mon Sep 17 00:00:00 2001 From: Nick Winter <livelily@gmail.com> Date: Sun, 14 Jun 2015 12:49:45 -0700 Subject: [PATCH 07/18] Hopefully making Systems editable by Artisans. --- server/levels/systems/level_system_handler.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/levels/systems/level_system_handler.coffee b/server/levels/systems/level_system_handler.coffee index eac89caca..4c356f1d7 100644 --- a/server/levels/systems/level_system_handler.coffee +++ b/server/levels/systems/level_system_handler.coffee @@ -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() From 1c633a8ee40026013aa3e44f60ac496b0ee538aa Mon Sep 17 00:00:00 2001 From: David Liu <a.davidliu@gmail.com> Date: Sun, 14 Jun 2015 15:25:11 -0700 Subject: [PATCH 08/18] Show correct achievement earned dates on user page fixes #2524 --- app/templates/user/main-user-view.jade | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/user/main-user-view.jade b/app/templates/user/main-user-view.jade index 444ec9d3d..b1bb6ec13 100644 --- a/app/templates/user/main-user-view.jade +++ b/app/templates/user/main-user-view.jade @@ -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 From fd5a99b9d0a83f771a7f6b8b1f510a4533533d86 Mon Sep 17 00:00:00 2001 From: Mihbo <s_a_n_d@bigmir.net> Date: Mon, 15 Jun 2015 10:02:24 +0300 Subject: [PATCH 09/18] Update uk.coffee translated some more strings --- app/locale/uk.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/locale/uk.coffee b/app/locale/uk.coffee index 05edcae61..fc9e714b6 100644 --- a/app/locale/uk.coffee +++ b/app/locale/uk.coffee @@ -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." From fa8d31774511f46b6f079f7ce7f22c88ce7e1cf3 Mon Sep 17 00:00:00 2001 From: Ikuyadeu <ikuyadeu@yahoo.co.jp> Date: Mon, 15 Jun 2015 18:48:04 +0900 Subject: [PATCH 10/18] Update ja.coffee --- app/locale/ja.coffee | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/locale/ja.coffee b/app/locale/ja.coffee index 501dc2282..d680d7ca9 100644 --- a/app/locale/ja.coffee +++ b/app/locale/ja.coffee @@ -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" From aabae7082e32023c84433f85489a35e79685279a Mon Sep 17 00:00:00 2001 From: Lai Tuan <laituan245@kaist.ac.kr> Date: Mon, 15 Jun 2015 19:11:48 +0900 Subject: [PATCH 11/18] Add Contact section to About page #2822 --- app/locale/en.coffee | 4 ++++ app/templates/about.jade | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/locale/en.coffee b/app/locale/en.coffee index d49a4ee26..4bc8ef36f 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -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." diff --git a/app/templates/about.jade b/app/templates/about.jade index 4abc35e1e..cd217b502 100644 --- a/app/templates/about.jade +++ b/app/templates/about.jade @@ -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 From e8a862b2cedf1bd6fead9f88d9a65762dffe289f Mon Sep 17 00:00:00 2001 From: Ikuyadeu <ikuyadeu@yahoo.co.jp> Date: Tue, 16 Jun 2015 11:05:18 +0900 Subject: [PATCH 12/18] Update ja.coffee --- app/locale/ja.coffee | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/app/locale/ja.coffee b/app/locale/ja.coffee index d680d7ca9..97d4836dc 100644 --- a/app/locale/ja.coffee +++ b/app/locale/ja.coffee @@ -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人の新しいヒーローとマスターのユニークなスキル" From ce73d87f92b342eb30bfa450d755e50ce3ca631a Mon Sep 17 00:00:00 2001 From: Mihbo <s_a_n_d@bigmir.net> Date: Tue, 16 Jun 2015 10:27:32 +0300 Subject: [PATCH 13/18] Update uk.coffee some more strings translated --- app/locale/uk.coffee | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/app/locale/uk.coffee b/app/locale/uk.coffee index fc9e714b6..6afe623db 100644 --- a/app/locale/uk.coffee +++ b/app/locale/uk.coffee @@ -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: "не розпочато" From 624dabd55a7c041bb27302c1de303657914fac00 Mon Sep 17 00:00:00 2001 From: Matt Lott <mattlott@live.com> Date: Tue, 16 Jun 2015 11:12:20 -0700 Subject: [PATCH 14/18] Level completion counts script --- .../mongodb/queries/levelCompletionCounts.js | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 scripts/analytics/mongodb/queries/levelCompletionCounts.js diff --git a/scripts/analytics/mongodb/queries/levelCompletionCounts.js b/scripts/analytics/mongodb/queries/levelCompletionCounts.js new file mode 100644 index 000000000..de7e91ab8 --- /dev/null +++ b/scripts/analytics/mongodb/queries/levelCompletionCounts.js @@ -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; +} From 593f7a9dd7ec5b845ad37d2cb10a14471fb8e9cd Mon Sep 17 00:00:00 2001 From: Nick Winter <livelily@gmail.com> Date: Tue, 16 Jun 2015 13:50:33 -0700 Subject: [PATCH 15/18] Some improvements for handling new art. --- README.md | 7 +++++++ app/assets/images/pages/about/jose_small.png | Bin 0 -> 12436 bytes app/assets/images/pages/about/oleg_small.png | Bin 0 -> 9562 bytes app/assets/images/pages/about/pavel_small.png | Bin 0 -> 10492 bytes app/lib/surface/SingularSprite.coffee | 2 +- app/templates/about.jade | 12 +++++++----- app/views/editor/thang/ThangTypeEditView.coffee | 1 + app/views/play/menu/InventoryModal.coffee | 7 +++++-- 8 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 app/assets/images/pages/about/jose_small.png create mode 100644 app/assets/images/pages/about/oleg_small.png create mode 100644 app/assets/images/pages/about/pavel_small.png diff --git a/README.md b/README.md index ba5af9e83..2d10a11ac 100644 --- a/README.md +++ b/README.md @@ -69,3 +69,10 @@ Whether you're novice or pro, the CodeCombat team is ready to help you implement    + + + + + + + diff --git a/app/assets/images/pages/about/jose_small.png b/app/assets/images/pages/about/jose_small.png new file mode 100644 index 0000000000000000000000000000000000000000..01bb440a9e6f6a339a6dbc7d6d4a48e6f5d874ee GIT binary patch literal 12436 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 LQUcXTn1uW<B<hN- literal 0 HcmV?d00001 diff --git a/app/assets/images/pages/about/oleg_small.png b/app/assets/images/pages/about/oleg_small.png new file mode 100644 index 0000000000000000000000000000000000000000..4690ed5cc03b398d1fb375fa7ed1b8edbc4fb24c GIT binary patch literal 9562 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 DUUafS literal 0 HcmV?d00001 diff --git a/app/assets/images/pages/about/pavel_small.png b/app/assets/images/pages/about/pavel_small.png new file mode 100644 index 0000000000000000000000000000000000000000..336e3e6e63ac1ea911a2ea46e5f789389889e077 GIT binary patch literal 10492 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%0Aku>ZE6kMd5s$4N)y4$EHTLe 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 literal 0 HcmV?d00001 diff --git a/app/lib/surface/SingularSprite.coffee b/app/lib/surface/SingularSprite.coffee index 86a79586a..2350fcda5 100644 --- a/app/lib/surface/SingularSprite.coffee +++ b/app/lib/surface/SingularSprite.coffee @@ -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 diff --git a/app/templates/about.jade b/app/templates/about.jade index cd217b502..8fbf2577f 100644 --- a/app/templates/about.jade +++ b/app/templates/about.jade @@ -92,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") @@ -143,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 @@ -155,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 @@ -165,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 diff --git a/app/views/editor/thang/ThangTypeEditView.coffee b/app/views/editor/thang/ThangTypeEditView.coffee index df722f079..b79d3ff9a 100644 --- a/app/views/editor/thang/ThangTypeEditView.coffee +++ b/app/views/editor/thang/ThangTypeEditView.coffee @@ -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.' diff --git a/app/views/play/menu/InventoryModal.coffee b/app/views/play/menu/InventoryModal.coffee index ed4f5a155..324412723 100644 --- a/app/views/play/menu/InventoryModal.coffee +++ b/app/views/play/menu/InventoryModal.coffee @@ -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 From 5c240d89cd79eb3069bfc227009083355e9b0945 Mon Sep 17 00:00:00 2001 From: Matt Lott <mattlott@live.com> Date: Wed, 17 Jun 2015 16:31:40 -0700 Subject: [PATCH 16/18] Cancel recipient subscriptions immediately --- server/payments/subscription_handler.coffee | 53 ++++++++++++++----- server/routes/stripe.coffee | 4 ++ .../functional/subscription.spec.coffee | 45 +++++++--------- 3 files changed, 62 insertions(+), 40 deletions(-) diff --git a/server/payments/subscription_handler.coffee b/server/payments/subscription_handler.coffee index 5cd7e300b..f2a3b34b0 100644 --- a/server/payments/subscription_handler.coffee +++ b/server/payments/subscription_handler.coffee @@ -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() diff --git a/server/routes/stripe.coffee b/server/routes/stripe.coffee index 0ef5abb96..0e46de9c6 100644 --- a/server/routes/stripe.coffee +++ b/server/routes/stripe.coffee @@ -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) diff --git a/test/server/functional/subscription.spec.coffee b/test/server/functional/subscription.spec.coffee index 9d9384800..bbf3815bd 100644 --- a/test/server/functional/subscription.spec.coffee +++ b/test/server/functional/subscription.spec.coffee @@ -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) From 5fb8b18a31791e46af619f7675f1e14f01f0e10f Mon Sep 17 00:00:00 2001 From: Matt Lott <mattlott@live.com> Date: Wed, 17 Jun 2015 16:35:12 -0700 Subject: [PATCH 17/18] :bug:Fix managed subs next payment UI --- app/views/account/SubscriptionView.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/account/SubscriptionView.coffee b/app/views/account/SubscriptionView.coffee index 317b4fc19..4fc341b45 100644 --- a/app/views/account/SubscriptionView.coffee +++ b/app/views/account/SubscriptionView.coffee @@ -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() From 6a4efa9a216ed5705f5ed5e51bbe9fc7e69e7e4f Mon Sep 17 00:00:00 2001 From: Nick Winter <livelily@gmail.com> Date: Wed, 17 Jun 2015 18:17:07 -0700 Subject: [PATCH 18/18] Switching to Boom and Bust as first level of the forest. --- app/views/play/CampaignView.coffee | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/views/play/CampaignView.coffee b/app/views/play/CampaignView.coffee index d2629223b..6c0199134 100644 --- a/app/views/play/CampaignView.coffee +++ b/app/views/play/CampaignView.coffee @@ -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 @@ -354,7 +353,7 @@ module.exports = class CampaignView extends RootView particleKey.push 'premium' if level.requiresSubscription 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('-')