mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-29 14:19:48 -04:00
commit
03e5d1ca50
27 changed files with 270 additions and 179 deletions
app
lib
locale
models
schemas/models
styles
templates/play/level/modal
views
bin
package.jsonserver
test/server
|
@ -1,4 +1,4 @@
|
|||
{backboneFailure, genericFailure} = require 'lib/errors'
|
||||
{backboneFailure, genericFailure, parseServerError} = require 'lib/errors'
|
||||
User = require 'models/User'
|
||||
storage = require 'lib/storage'
|
||||
BEEN_HERE_BEFORE_KEY = 'beenHereBefore'
|
||||
|
@ -16,7 +16,14 @@ init = ->
|
|||
module.exports.createUser = (userObject, failure=backboneFailure, nextURL=null) ->
|
||||
user = new User(userObject)
|
||||
user.save({}, {
|
||||
error: failure,
|
||||
error: (model,jqxhr,options) ->
|
||||
error = parseServerError(jqxhr.responseText)
|
||||
property = error.property if error.property
|
||||
if jqxhr.status is 409 and property is 'name'
|
||||
anonUserObject = _.omit(userObject, 'name')
|
||||
module.exports.createUser anonUserObject, failure, nextURL
|
||||
else
|
||||
genericFailure(jqxhr)
|
||||
success: -> if nextURL then window.location.href = nextURL else window.location.reload()
|
||||
})
|
||||
|
||||
|
|
|
@ -103,10 +103,8 @@ module.exports.getConflicts = (headDeltas, pendingDeltas) ->
|
|||
pendingPathMap = groupDeltasByAffectingPaths(pendingDeltas)
|
||||
paths = _.keys(headPathMap).concat(_.keys(pendingPathMap))
|
||||
|
||||
# Here's my thinking:
|
||||
# A) Conflicts happen when one delta path is a substring of another delta path
|
||||
# B) A delta from one self-consistent group cannot conflict with another
|
||||
# So, sort the paths, which will naturally make conflicts adjacent,
|
||||
# Here's my thinking: conflicts happen when one delta path is a substring of another delta path
|
||||
# So, sort paths from both deltas together, which will naturally make conflicts adjacent,
|
||||
# and if one is identified, one path is from the headDeltas, the other is from pendingDeltas
|
||||
# This is all to avoid an O(nm) brute force search.
|
||||
|
||||
|
@ -141,7 +139,27 @@ groupDeltasByAffectingPaths = (deltas) ->
|
|||
delta: delta
|
||||
path: (item.toString() for item in path).join('/')
|
||||
}
|
||||
_.groupBy metaDeltas, 'path'
|
||||
|
||||
map = _.groupBy metaDeltas, 'path'
|
||||
|
||||
# Turns out there are cases where a single delta can include paths
|
||||
# that 'conflict' with each other, ie one is a substring of the other
|
||||
# because of moved indices. To handle this case, go through and prune
|
||||
# out all deeper paths that conflict with more shallow paths, so
|
||||
# getConflicts path checking works properly.
|
||||
|
||||
paths = _.keys(map)
|
||||
return map unless paths.length
|
||||
paths.sort()
|
||||
prunedMap = {}
|
||||
previousPath = paths[0]
|
||||
for path, i in paths
|
||||
continue if i is 0
|
||||
continue if path.startsWith previousPath
|
||||
prunedMap[path] = map[path]
|
||||
previousPath = path
|
||||
|
||||
prunedMap
|
||||
|
||||
module.exports.pruneConflictsFromDelta = (delta, conflicts) ->
|
||||
# the jsondiffpatch delta mustn't include any dangling nodes,
|
||||
|
|
|
@ -237,12 +237,12 @@ module.exports = class GoalManager extends CocoClass
|
|||
# saveThangs: by default we would want to save all the Thangs, which means that we would want none of them to be "done"
|
||||
numNeeded = _.size(stateThangs) - Math.min((goal.howMany ? 1), _.size stateThangs) + 1
|
||||
numDone = _.filter(stateThangs).length
|
||||
#console.log "needed", numNeeded, "done", numDone, "of total", _.size(stateThangs), "with how many", goal.howMany, "and stateThangs", stateThangs
|
||||
console.log "needed", numNeeded, "done", numDone, "of total", _.size(stateThangs), "with how many", goal.howMany, "and stateThangs", stateThangs
|
||||
return unless numDone >= numNeeded
|
||||
return if state.status and not success # already failed it; don't wipe keyframe
|
||||
state.status = if success then "success" else "failure"
|
||||
state.keyFrame = frameNumber
|
||||
#console.log goalID, "became", success, "on frame", frameNumber, "with overallStatus", @checkOverallStatus true
|
||||
console.log goalID, "became", success, "on frame", frameNumber, "with overallStatus", @checkOverallStatus true
|
||||
if overallStatus = @checkOverallStatus true
|
||||
matchedGoals = (_.find(@goals, {id: goalID}) for goalID, goalState of @goalStates when goalState.status is overallStatus)
|
||||
mostEagerGoal = _.min matchedGoals, 'worldEndsAfter'
|
||||
|
|
|
@ -246,6 +246,7 @@
|
|||
multiplayer_hint_label: "Hint:"
|
||||
multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
|
||||
multiplayer_coming_soon: "More multiplayer features to come!"
|
||||
multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
|
||||
guide_title: "Guide"
|
||||
tome_minion_spells: "Your Minions' Spells"
|
||||
tome_read_only_spells: "Read-Only Spells"
|
||||
|
@ -710,3 +711,4 @@
|
|||
user_names: "User Names"
|
||||
files: "Files"
|
||||
top_simulators: "Top Simulators"
|
||||
source_document: "Source Document"
|
||||
|
|
|
@ -15,9 +15,9 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
fork: "Fork"
|
||||
play: "Spelen"
|
||||
retry: "Probeer opnieuw"
|
||||
# watch: "Watch"
|
||||
# unwatch: "Unwatch"
|
||||
# submit_patch: "Submit Patch"
|
||||
watch: "Volgen"
|
||||
unwatch: "Ontvolgen"
|
||||
submit_patch: "Correctie Opsturen"
|
||||
|
||||
units:
|
||||
second: "seconde"
|
||||
|
@ -36,11 +36,11 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
|
||||
nav:
|
||||
play: "Levels"
|
||||
# community: "Community"
|
||||
community: "Gemeenschap"
|
||||
editor: "Editor"
|
||||
blog: "Blog"
|
||||
forum: "Forum"
|
||||
# account: "Account"
|
||||
account: "Lidmaatschap"
|
||||
admin: "Administrator"
|
||||
home: "Home"
|
||||
contribute: "Bijdragen"
|
||||
|
@ -152,7 +152,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
autosave: "Aanpassingen Automatisch Opgeslagen"
|
||||
me_tab: "Ik"
|
||||
picture_tab: "Afbeelding"
|
||||
# upload_picture: "Upload a picture"
|
||||
upload_picture: "Je afbeelding opsturen"
|
||||
wizard_tab: "Tovenaar"
|
||||
password_tab: "Wachtwoord"
|
||||
emails_tab: "Emails"
|
||||
|
@ -163,12 +163,12 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
email_subscriptions: "E-mail Abonnementen"
|
||||
email_announcements: "Aankondigingen"
|
||||
email_announcements_description: "Verkrijg emails over het laatste nieuws en de ontwikkelingen bij CodeCombat."
|
||||
email_notifications: "Notificaties"
|
||||
# email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity."
|
||||
# email_any_notes: "Any Notifications"
|
||||
# email_any_notes_description: "Disable to stop all activity notification emails."
|
||||
# email_recruit_notes: "Job Opportunities"
|
||||
# email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job."
|
||||
email_notifications: "Meldingen"
|
||||
email_notifications_summary: "Instellingen voor gepersonaliseerde, automatische meldingen via e-mail omtrent je activiteit op CodeCombat."
|
||||
email_any_notes: "Alle Meldingen"
|
||||
email_any_notes_description: "Zet alle activiteit-meldingen via e-mail af."
|
||||
email_recruit_notes: "Job Aanbiedingen"
|
||||
email_recruit_notes_description: "Als je zeer goed speelt, zouden we je wel eens kunnen contacteren om je een (betere) job aan te bieden."
|
||||
contributor_emails: "Medewerker Klasse emails"
|
||||
contribute_prefix: "We zoeken mensen om met ons te komen feesten! Bekijk de "
|
||||
contribute_page: "bijdragepagina"
|
||||
|
@ -180,8 +180,8 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
job_profile: "Job Profiel"
|
||||
job_profile_approved: "Jouw job profiel werd goedgekeurd door CodeCombat. Werkgevers zullen het kunnen bekijken totdat je het inactief zet of als er geen verandering in komt voor vier weken."
|
||||
job_profile_explanation: "Hey! Vul dit in en we zullen je contacteren om je een job als softwareontwikkelaar te helpen vinden."
|
||||
# sample_profile: "See a sample profile"
|
||||
# view_profile: "View Your Profile"
|
||||
sample_profile: "Bekijk een voorbeeld kandidaat-profiel"
|
||||
view_profile: "Bekijk je eigen kandidaat-profiel"
|
||||
|
||||
account_profile:
|
||||
edit_settings: "Instellingen Aanpassen"
|
||||
|
@ -199,7 +199,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
|
||||
employers:
|
||||
want_to_hire_our_players: "Wil je expert CodeCombat spelers aanwerven? "
|
||||
# see_candidates: "Click here to see our candidates"
|
||||
see_candidates: "Klik om je kandidaten te zien"
|
||||
candidates_count_prefix: "Momenteel hebben we "
|
||||
candidates_count_many: "veel"
|
||||
candidates_count_suffix: "zeer getalenteerde en ervaren ontwikkelaars die werk zoeken."
|
||||
|
@ -210,8 +210,8 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
candidate_top_skills: "Beste vaardigheden"
|
||||
candidate_years_experience: "Jaren ervaring"
|
||||
candidate_last_updated: "Laatst aangepast"
|
||||
# candidate_approved: "Us?"
|
||||
# candidate_active: "Them?"
|
||||
candidate_approved: "Wij?"
|
||||
candidate_active: "Zij?"
|
||||
|
||||
play_level:
|
||||
level_load_error: "Level kon niet geladen worden: "
|
||||
|
@ -257,7 +257,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
tome_select_spell: "Selecteer een Spreuk"
|
||||
tome_select_a_thang: "Selecteer Iemand voor "
|
||||
tome_available_spells: "Beschikbare spreuken"
|
||||
hud_continue: "Ga verder (druk shift-space)"
|
||||
hud_continue: "Ga verder (druk shift-spatie)"
|
||||
spell_saved: "Spreuk Opgeslagen"
|
||||
skip_tutorial: "Overslaan (esc)"
|
||||
editor_config: "Editor Configuratie"
|
||||
|
@ -268,7 +268,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
editor_config_keybindings_default: "Standaard (Ace)"
|
||||
editor_config_keybindings_description: "Voeg extra shortcuts toe van de gebruikelijke editors."
|
||||
editor_config_invisibles_label: "Toon onzichtbare"
|
||||
editor_config_invisibles_description: "Toon onzichtbare whitespace karakters."
|
||||
editor_config_invisibles_description: "Toon onzichtbare (spatie) karakters."
|
||||
editor_config_indentguides_label: "Toon inspringing regels"
|
||||
editor_config_indentguides_description: "Toon verticale hulplijnen om de zichtbaarheid te verbeteren."
|
||||
editor_config_behaviors_label: "Slim gedrag"
|
||||
|
@ -288,9 +288,9 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
tip_debugging_program: "Als debuggen het proces is om bugs te verwijderen, dan moet programmeren het proces zijn om ze erin te stoppen. - Edsger W. Dijkstra"
|
||||
tip_forums: "Ga naar de forums en vertel ons wat je denkt!"
|
||||
tip_baby_coders: "Zelfs babies zullen in de toekomst een Tovenaar zijn."
|
||||
tip_morale_improves: "Het spel zal blijven laden tot de moreel verbeterd."
|
||||
tip_morale_improves: "Het spel zal blijven laden tot de moreel verbetert."
|
||||
tip_all_species: "Wij geloven in gelijke kansen voor alle wezens om te leren programmeren."
|
||||
# tip_reticulating: "Reticulating spines."
|
||||
tip_reticulating: "Paden aan het verknopen."
|
||||
tip_harry: "Je bent een tovenaar, "
|
||||
tip_great_responsibility: "Met een groots talent voor programmeren komt een grootse debug verantwoordelijkheid."
|
||||
tip_munchkin: "Als je je groentjes niet opeet zal een munchkin je ontvoeren terwijl je slaapt."
|
||||
|
@ -320,12 +320,12 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
lg_title: "Laatste Spelletjes"
|
||||
clas: "CLAs"
|
||||
|
||||
# community:
|
||||
# level_editor: "Level Editor"
|
||||
# main_title: "CodeCombat Community"
|
||||
# facebook: "Facebook"
|
||||
# twitter: "Twitter"
|
||||
# gplus: "Google+"
|
||||
community:
|
||||
level_editor: "Level Bewerker"
|
||||
main_title: "CodeCombat Gemeenschap"
|
||||
facebook: "Facebook"
|
||||
twitter: "Twitter"
|
||||
gplus: "Google+"
|
||||
|
||||
editor:
|
||||
main_title: "CodeCombat Editors"
|
||||
|
@ -336,7 +336,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
thang_description: "Maak eenheden, beschrijf hun standaard logica, graphics en audio. Momenteel is enkel het importeren van vector graphics geëxporteerd uit Flash ondersteund."
|
||||
level_title: "Level Editor"
|
||||
level_description: "Bevat de benodigdheden om scripts te schrijven, audio te uploaden en aangepaste logica te creëren om alle soorten levels te maken. Het is alles wat wij zelf ook gebruiken!"
|
||||
# got_questions: "Questions about using the CodeCombat editors?"
|
||||
got_questions: "Heb je vragen over het gebruik van de CodeCombat editors?"
|
||||
contact_us: "contacteer ons!"
|
||||
hipchat_prefix: "Je kan ons ook vinden in ons"
|
||||
hipchat_url: "(Engelstalig) HipChat kanaal."
|
||||
|
@ -378,9 +378,9 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
new_article_title: "Maak een Nieuw Artikel"
|
||||
new_thang_title: "Maak een Nieuw Thang Type"
|
||||
new_level_title: "Maak een Nieuw Level"
|
||||
# new_article_title_signup: "Sign Up to Create a New Article"
|
||||
# new_thang_title_signup: "Sign Up to Create a New Thang Type"
|
||||
# new_level_title_signup: "Sign Up to Create a New Level"
|
||||
new_article_title_signup: "Meld je aan om een Nieuw Artikel te maken"
|
||||
new_thang_title_signup: "Meld je aan op een Nieuw Thang Type te maken"
|
||||
new_level_title_signup: "Meld je aan om een Nieuw Level te maken"
|
||||
article_search_title: "Zoek Artikels Hier"
|
||||
thang_search_title: "Zoek Thang Types Hier"
|
||||
level_search_title: "Zoek Levels Hier"
|
||||
|
@ -650,13 +650,13 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
simple_ai: "Simpele AI"
|
||||
warmup: "Opwarming"
|
||||
vs: "tegen"
|
||||
# friends_playing: "Friends Playing"
|
||||
# sign_up_for_friends: "Sign up to play with your friends!"
|
||||
# social_connect_blurb: "Connect and play against your friends!"
|
||||
# invite_friends_to_battle: "Invite your friends to join you in battle!"
|
||||
# fight: "Fight!"
|
||||
# watch_victory: "Watch your victory"
|
||||
# defeat_the: "Defeat the"
|
||||
friends_playing: "Spelende Vrienden"
|
||||
sign_up_for_friends: "Meld je aan om met je vrienden te spelen!"
|
||||
social_connect_blurb: "Koppel je sociaal netwerk om tegen je vrienden te spelen!"
|
||||
invite_friends_to_battle: "Nodig je vrienden uit om deel te nemen aan het gevecht!"
|
||||
fight: "Aanvallen!"
|
||||
watch_victory: "Aanschouw je overwinning!"
|
||||
defeat_the: "Versla de"
|
||||
|
||||
multiplayer_launch:
|
||||
introducing_dungeon_arena: "Introductie van Dungeon Arena"
|
||||
|
@ -664,7 +664,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
to_battle: "Naar het slagveld, ontwikkelaars!"
|
||||
modern_day_sorcerer: "Kan jij programmeren? Dat is pas stoer. Jij bent een moderne tovenaar! Is het niet tijd dat je jouw magische krachten gebruikt voor het besturen van jou minions in het slagveld? En nee, we praten hier niet over robots."
|
||||
arenas_are_here: "CodeCombat's kop aan kop multiplayer arena's zijn er."
|
||||
ladder_explanation: "Kies jouw helden, betover jouw mensen of ogre legers, en beklim jouw weg naar de top in de ladder, door het verslagen van vriend en vijand. Daag nu je vrienden uit in de multiplayer programmeer arena's en verdien eeuwige roem. Indien je creatief bent, kan je zelfs"
|
||||
ladder_explanation: "Kies jouw helden, betover jouw mensen of ork-legers, en beklim jouw weg naar de top in de ladder, door het verslagen van vriend en vijand. Daag nu je vrienden uit in de multiplayer programmeer arena's en verdien eeuwige roem. Indien je creatief bent, kan je zelfs"
|
||||
fork_our_arenas: "onze arenas forken"
|
||||
create_worlds: "en jouw eigen werelden creëren."
|
||||
javascript_rusty: "Jouw JavaScript is een beetje roestig? Wees niet bang, er is een"
|
||||
|
@ -699,14 +699,14 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
user_profile: "Gebruikersprofiel"
|
||||
patches: "Patches"
|
||||
model: "Model"
|
||||
# system: "System"
|
||||
# component: "Component"
|
||||
# components: "Components"
|
||||
# thang: "Thang"
|
||||
# thangs: "Thangs"
|
||||
# level_session: "Your Session"
|
||||
# opponent_session: "Opponent Session"
|
||||
# article: "Article"
|
||||
# user_names: "User Names"
|
||||
# files: "Files"
|
||||
# top_simulators: "Top Simulators"
|
||||
system: "Systeem"
|
||||
component: "Component"
|
||||
components: "Componenten"
|
||||
thang: "Thang"
|
||||
thangs: "Thangs"
|
||||
level_session: "Jouw Sessie"
|
||||
opponent_session: "Sessie van tegenstander"
|
||||
article: "Artikel"
|
||||
user_names: "Gebruikersnamen"
|
||||
files: "Bestanden"
|
||||
top_simulators: "Top Simulatoren"
|
||||
|
|
|
@ -15,9 +15,9 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
fork: "派生"
|
||||
play: "开始"
|
||||
retry: "重试"
|
||||
# watch: "Watch"
|
||||
# unwatch: "Unwatch"
|
||||
# submit_patch: "Submit Patch"
|
||||
watch: "关注"
|
||||
unwatch: "取消关注"
|
||||
submit_patch: "提交补丁"
|
||||
|
||||
units:
|
||||
second: "秒"
|
||||
|
@ -166,8 +166,8 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
email_notifications: "通知"
|
||||
# email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity."
|
||||
email_any_notes: "任何通知"
|
||||
# email_any_notes_description: "Disable to stop all activity notification emails."
|
||||
# email_recruit_notes: "Job Opportunities"
|
||||
email_any_notes_description: "取消接收所有活动提醒邮件"
|
||||
email_recruit_notes: "工作机会"
|
||||
# email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job."
|
||||
contributor_emails: "贡献者通知"
|
||||
contribute_prefix: "我们在寻找志同道合的人!请到"
|
||||
|
@ -181,7 +181,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
job_profile_approved: "你填写的工作经历将由CodeCombat认证. 雇主将看到这些信息,除非你将它设置为不启用状态或者连续四周没有更新."
|
||||
job_profile_explanation: "你好! 填写这些信息, 我们将使用它帮你寻找一份软件开发的工作."
|
||||
# sample_profile: "See a sample profile"
|
||||
# view_profile: "View Your Profile"
|
||||
view_profile: "浏览个人信息"
|
||||
|
||||
account_profile:
|
||||
edit_settings: "编辑设置"
|
||||
|
@ -199,7 +199,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
|
||||
employers:
|
||||
want_to_hire_our_players: "想要雇用CodeCombat上的专业玩家?"
|
||||
# see_candidates: "Click here to see our candidates"
|
||||
see_candidates: "点击这里查看我们的忧患人"
|
||||
candidates_count_prefix: "我们当前有 "
|
||||
candidates_count_many: "很多"
|
||||
candidates_count_suffix: "经过我们认证的高手们正在找工作。"
|
||||
|
@ -210,8 +210,8 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
candidate_top_skills: "高级技能"
|
||||
candidate_years_experience: "多年工作经验"
|
||||
candidate_last_updated: "最后一次更新"
|
||||
# candidate_approved: "Us?"
|
||||
# candidate_active: "Them?"
|
||||
candidate_approved: "我们"
|
||||
candidate_active: "他们"
|
||||
|
||||
play_level:
|
||||
level_load_error: "关卡不能载入: "
|
||||
|
@ -271,7 +271,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
editor_config_invisibles_description: "显示诸如空格或TAB键。"
|
||||
editor_config_indentguides_label: "显示缩进提示"
|
||||
editor_config_indentguides_description: "显示一条竖线以使缩进更明显。"
|
||||
# editor_config_behaviors_label: "Smart Behaviors"
|
||||
editor_config_behaviors_label: "聪明的行为"
|
||||
editor_config_behaviors_description: "自动完成括号,大括号和引号。"
|
||||
loading_ready: "载入完成!"
|
||||
tip_insert_positions: "使用Shift+左键来插入拼写编辑器。"
|
||||
|
@ -320,7 +320,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
lg_title: "最新的游戏"
|
||||
# clas: "CLAs"
|
||||
|
||||
# community:
|
||||
community:
|
||||
level_editor: "关卡编辑器"
|
||||
main_title: "CodeCombat 社区"
|
||||
# facebook: "Facebook"
|
||||
|
@ -336,7 +336,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
thang_description: "创建单元,并定义单元的逻辑、图形和音频。目前只支持导入 Flash 导出的矢量图形。"
|
||||
level_title: "关卡编辑器"
|
||||
level_description: "所有用来创造所有难度的关卡的工具,包括脚本、上传音频和构建自定义逻辑。"
|
||||
# got_questions: "Questions about using the CodeCombat editors?"
|
||||
got_questions: "使用CodeCombat编辑器有问题?"
|
||||
contact_us: "联系我们!"
|
||||
hipchat_prefix: "你也可以在这里找到我们"
|
||||
hipchat_url: "HipChat 房间。"
|
||||
|
@ -378,9 +378,9 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
|
|||
new_article_title: "创建一个新物品"
|
||||
new_thang_title: "创建一个新物品类型"
|
||||
new_level_title: "创建一个新关卡"
|
||||
# new_article_title_signup: "Sign Up to Create a New Article"
|
||||
# new_thang_title_signup: "Sign Up to Create a New Thang Type"
|
||||
# new_level_title_signup: "Sign Up to Create a New Level"
|
||||
new_article_title_signup: "注册以创建新的物品"
|
||||
new_thang_title_signup: "注册以创建新的物品类型"
|
||||
new_level_title_signup: "注册以创建新的关卡"
|
||||
article_search_title: "在这里搜索物品"
|
||||
thang_search_title: "在这里搜索物品类型"
|
||||
level_search_title: "在这里搜索关卡"
|
||||
|
|
|
@ -24,3 +24,15 @@ module.exports = class LevelSession extends CocoModel
|
|||
code = @get('code')
|
||||
parts = spellKey.split '/'
|
||||
code?[parts[0]]?[parts[1]]
|
||||
|
||||
readyToRank: ->
|
||||
return false unless @get('levelID') # If it hasn't been denormalized, then it's not ready.
|
||||
return false unless c1 = @get('code')
|
||||
return false unless team = @get('team')
|
||||
return true unless c2 = @get('submittedCode')
|
||||
thangSpellArr = (s.split("/") for s in @get('teamSpells')[team])
|
||||
for item in thangSpellArr
|
||||
thang = item[0]
|
||||
spell = item[1]
|
||||
return true if c1[thang][spell] isnt c2[thang][spell]
|
||||
false
|
||||
|
|
|
@ -4,6 +4,7 @@ ThangComponentSchema = require './thang_component'
|
|||
SpecificArticleSchema = c.object()
|
||||
c.extendNamedProperties SpecificArticleSchema # name first
|
||||
SpecificArticleSchema.properties.body = { type: 'string', title: 'Content', description: "The body content of the article, in Markdown.", format: 'markdown' }
|
||||
SpecificArticleSchema.properties.i18n = {type: "object", format: 'i18n', props: ['name', 'body'], description: "Help translate this article"}
|
||||
SpecificArticleSchema.displayProperty = 'name'
|
||||
|
||||
side = {title: "Side", description: "A side.", type: 'string', 'enum': ['left', 'right', 'top', 'bottom']}
|
||||
|
|
|
@ -50,6 +50,9 @@
|
|||
#settings-col
|
||||
float: left
|
||||
width: 550px
|
||||
|
||||
.treema-row img
|
||||
max-width: 100%
|
||||
|
||||
#thang-type-treema
|
||||
height: 400px
|
||||
|
@ -66,6 +69,8 @@
|
|||
background-color: white
|
||||
border-radius: 4px
|
||||
|
||||
|
||||
|
||||
#spritesheets
|
||||
border: 1px solid green
|
||||
max-width: 100%
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
display: none
|
||||
#docs-button
|
||||
display: none
|
||||
#gold-view
|
||||
right: 1%
|
||||
|
||||
#control-bar-view
|
||||
width: 100%
|
||||
|
@ -41,7 +43,7 @@
|
|||
margin: 0 auto
|
||||
|
||||
canvas#surface
|
||||
background-color: #ddd
|
||||
background-color: #333
|
||||
max-height: 93%
|
||||
max-height: -webkit-calc(100% - 60px)
|
||||
max-height: calc(100% - 60px)
|
||||
|
|
|
@ -27,10 +27,11 @@ block modal-body-content
|
|||
|
||||
if ladderGame
|
||||
if me.get('anonymous')
|
||||
p Sign in or create an account and get your solution on the leaderboard!
|
||||
p(data-i18n="play_level.multiplayer_sign_in_leaderboard") Sign in or create an account and get your solution on the leaderboard.
|
||||
else if readyToRank
|
||||
button.btn.btn-success.rank-game-button(data-i18n="play_level.victory_rank_my_game") Rank My Game
|
||||
else
|
||||
a#go-to-leaderboard-button.btn.btn-primary(href="/play/ladder/#{levelSlug}#my-matches") Go to the leaderboard!
|
||||
p You can submit your game to be ranked from the leaderboard page.
|
||||
a.btn.btn-primary(href="/play/ladder/#{levelSlug}#my-matches", data-i18n="play_level.victory_go_ladder") Return to Ladder
|
||||
|
||||
block modal-footer-content
|
||||
a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="modal.close").btn.btn-primary Close
|
||||
|
|
|
@ -17,14 +17,11 @@ module.exports = class PatchModal extends ModalView
|
|||
constructor: (@patch, @targetModel, options) ->
|
||||
super(options)
|
||||
targetID = @patch.get('target').id
|
||||
if false
|
||||
@originalSource = targetModel.clone(false)
|
||||
@onOriginalLoaded()
|
||||
if targetID is @targetModel.id
|
||||
@originalSource = @targetModel.clone(false)
|
||||
else
|
||||
@originalSource = new targetModel.constructor({_id:targetID})
|
||||
@originalSource.fetch()
|
||||
@listenToOnce @originalSource, 'sync', @onOriginalLoaded
|
||||
@addResourceToLoad(@originalSource)
|
||||
@originalSource = new @targetModel.constructor({_id:targetID})
|
||||
@supermodel.loadModel @originalSource, 'source_document'
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
|
@ -35,7 +32,7 @@ module.exports = class PatchModal extends ModalView
|
|||
c
|
||||
|
||||
afterRender: ->
|
||||
return if @originalSource.loading
|
||||
return unless @supermodel.finished()
|
||||
headModel = null
|
||||
if @targetModel.hasWriteAccess()
|
||||
headModel = @originalSource.clone(false)
|
||||
|
|
|
@ -45,7 +45,7 @@ module.exports = class LadderTabView extends CocoView
|
|||
checkFriends: ->
|
||||
return if @checked or (not window.FB) or (not window.gapi)
|
||||
@checked = true
|
||||
|
||||
|
||||
# @addSomethingToLoad("facebook_status")
|
||||
|
||||
@fbStatusRes = @supermodel.addSomethingResource("facebook_status", 0)
|
||||
|
@ -78,7 +78,7 @@ module.exports = class LadderTabView extends CocoView
|
|||
@fbFriendRes.load()
|
||||
|
||||
FB.api '/me/friends', @onFacebookFriendsLoaded
|
||||
|
||||
|
||||
onFacebookFriendsLoaded: (response) =>
|
||||
@facebookData = response.data
|
||||
@loadFacebookFriendSessions()
|
||||
|
@ -104,7 +104,7 @@ module.exports = class LadderTabView extends CocoView
|
|||
friend.otherTeam = if friend.team is 'humans' then 'ogres' else 'humans'
|
||||
friend.imageSource = "http://graph.facebook.com/#{friend.facebookID}/picture"
|
||||
@facebookFriendSessions = result
|
||||
|
||||
|
||||
# GOOGLE PLUS
|
||||
|
||||
onConnectGPlus: ->
|
||||
|
@ -113,7 +113,7 @@ module.exports = class LadderTabView extends CocoView
|
|||
application.gplusHandler.reauthorize()
|
||||
|
||||
onConnectedWithGPlus: -> location.reload() if @connecting
|
||||
|
||||
|
||||
gplusSessionStateLoaded: ->
|
||||
if application.gplusHandler.loggedIn
|
||||
#@addSomethingToLoad("gplus_friends")
|
||||
|
@ -146,7 +146,7 @@ module.exports = class LadderTabView extends CocoView
|
|||
friend.otherTeam = if friend.team is 'humans' then 'ogres' else 'humans'
|
||||
friend.imageSource = friendsMap[friend.gplusID].image.url
|
||||
@gplusFriendSessions = result
|
||||
|
||||
|
||||
# LADDER LOADING
|
||||
|
||||
refreshLadder: ->
|
||||
|
@ -160,7 +160,7 @@ module.exports = class LadderTabView extends CocoView
|
|||
|
||||
render: ->
|
||||
super()
|
||||
|
||||
|
||||
@$el.find('.histogram-display').each (i, el) =>
|
||||
histogramWrapper = $(el)
|
||||
team = _.find @teams, name: histogramWrapper.data('team-name')
|
||||
|
@ -168,8 +168,8 @@ module.exports = class LadderTabView extends CocoView
|
|||
$.when(
|
||||
$.get("/db/level/#{@level.get('slug')}/histogram_data?team=#{team.name.toLowerCase()}", (data) -> histogramData = data)
|
||||
).then =>
|
||||
@generateHistogram(histogramWrapper, histogramData, team.name.toLowerCase())
|
||||
|
||||
@generateHistogram(histogramWrapper, histogramData, team.name.toLowerCase()) unless @destroyed
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
ctx.level = @level
|
||||
|
@ -186,7 +186,7 @@ module.exports = class LadderTabView extends CocoView
|
|||
#renders twice, hack fix
|
||||
if $("#"+histogramElement.attr("id")).has("svg").length then return
|
||||
histogramData = histogramData.map (d) -> d*100
|
||||
|
||||
|
||||
margin =
|
||||
top: 20
|
||||
right: 20
|
||||
|
@ -195,17 +195,17 @@ module.exports = class LadderTabView extends CocoView
|
|||
|
||||
width = 300 - margin.left - margin.right
|
||||
height = 125 - margin.top - margin.bottom
|
||||
|
||||
|
||||
formatCount = d3.format(",.0")
|
||||
|
||||
|
||||
x = d3.scale.linear().domain([-3000,6000]).range([0,width])
|
||||
|
||||
data = d3.layout.histogram().bins(x.ticks(20))(histogramData)
|
||||
y = d3.scale.linear().domain([0,d3.max(data, (d) -> d.y)]).range([height,0])
|
||||
|
||||
|
||||
#create the x axis
|
||||
xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(5).outerTickSize(0)
|
||||
|
||||
|
||||
svg = d3.select("#"+histogramElement.attr("id")).append("svg")
|
||||
.attr("width", width + margin.left + margin.right)
|
||||
.attr("height", height + margin.top + margin.bottom)
|
||||
|
@ -214,13 +214,13 @@ module.exports = class LadderTabView extends CocoView
|
|||
barClass = "bar"
|
||||
if teamName.toLowerCase() is "ogres" then barClass = "ogres-bar"
|
||||
if teamName.toLowerCase() is "humans" then barClass = "humans-bar"
|
||||
|
||||
|
||||
bar = svg.selectAll(".bar")
|
||||
.data(data)
|
||||
.enter().append("g")
|
||||
.attr("class",barClass)
|
||||
.attr("transform", (d) -> "translate(#{x(d.x)},#{y(d.y)})")
|
||||
|
||||
.attr("transform", (d) -> "translate(#{x(d.x)},#{y(d.y)})")
|
||||
|
||||
bar.append("rect")
|
||||
.attr("x",1)
|
||||
.attr("width",width/20)
|
||||
|
@ -232,7 +232,7 @@ module.exports = class LadderTabView extends CocoView
|
|||
.enter().append("g")
|
||||
.attr("class","specialbar")
|
||||
.attr("transform", "translate(#{x(playerScore)},#{y(9001)})")
|
||||
|
||||
|
||||
scorebar.append("rect")
|
||||
.attr("x",1)
|
||||
.attr("width",3)
|
||||
|
@ -240,9 +240,9 @@ module.exports = class LadderTabView extends CocoView
|
|||
rankClass = "rank-text"
|
||||
if teamName.toLowerCase() is "ogres" then rankClass = "rank-text ogres-rank-text"
|
||||
if teamName.toLowerCase() is "humans" then rankClass = "rank-text humans-rank-text"
|
||||
|
||||
|
||||
message = "#{histogramData.length} players"
|
||||
if @leaderboards[teamName].session? then message="#{@leaderboards[teamName].myRank}/#{histogramData.length}"
|
||||
if @leaderboards[teamName].session? then message="##{@leaderboards[teamName].myRank} of #{histogramData.length}"
|
||||
svg.append("g")
|
||||
.append("text")
|
||||
.attr("class",rankClass)
|
||||
|
@ -250,14 +250,14 @@ module.exports = class LadderTabView extends CocoView
|
|||
.attr("text-anchor","end")
|
||||
.attr("x",width)
|
||||
.text(message)
|
||||
|
||||
|
||||
#Translate the x-axis up
|
||||
svg.append("g")
|
||||
.attr("class", "x axis")
|
||||
.attr("transform","translate(0," + height + ")")
|
||||
.call(xAxis)
|
||||
|
||||
|
||||
|
||||
|
||||
consolidateFriends: ->
|
||||
allFriendSessions = (@facebookFriendSessions or []).concat(@gplusFriendSessions or [])
|
||||
sessions = _.uniq allFriendSessions, false, (session) -> session._id
|
||||
|
@ -312,7 +312,7 @@ class LeaderboardData extends CocoClass
|
|||
@trigger 'sync', @
|
||||
# TODO: cache user ids -> names mapping, and load them here as needed,
|
||||
# and apply them to sessions. Fetching each and every time is too costly.
|
||||
|
||||
|
||||
onFail: (resource, jqxhr) =>
|
||||
return if @destroyed
|
||||
@trigger 'error', @, jqxhr
|
||||
|
|
|
@ -72,7 +72,7 @@ module.exports = class MyMatchesTabView extends CocoView
|
|||
|
||||
for team in @teams
|
||||
team.session = (s for s in @sessions.models when s.get('team') is team.id)[0]
|
||||
team.readyToRank = @readyToRank(team.session)
|
||||
team.readyToRank = team.session?.readyToRank()
|
||||
team.isRanking = team.session?.get('isRanking')
|
||||
team.matches = (convertMatch(match, team.session.get('submitDate')) for match in team.session?.get('matches') or [])
|
||||
team.matches.reverse()
|
||||
|
@ -84,7 +84,7 @@ module.exports = class MyMatchesTabView extends CocoView
|
|||
if scoreHistory?.length > 1
|
||||
team.scoreHistory = scoreHistory
|
||||
scoreHistory = _.last scoreHistory, 100 # Chart URL needs to be under 2048 characters for GET
|
||||
|
||||
|
||||
team.currentScore = Math.round scoreHistory[scoreHistory.length - 1][1] * 100
|
||||
team.chartColor = team.primaryColor.replace '#', ''
|
||||
#times = (s[0] for s in scoreHistory)
|
||||
|
@ -108,36 +108,35 @@ module.exports = class MyMatchesTabView extends CocoView
|
|||
sessionID = button.data('session-id')
|
||||
session = _.find @sessions.models, {id: sessionID}
|
||||
rankingState = 'unavailable'
|
||||
if @readyToRank session
|
||||
if session.readyToRank()
|
||||
rankingState = 'rank'
|
||||
else if session.get 'isRanking'
|
||||
rankingState = 'ranking'
|
||||
@setRankingButtonText button, rankingState
|
||||
|
||||
|
||||
@$el.find('.score-chart-wrapper').each (i, el) =>
|
||||
scoreWrapper = $(el)
|
||||
team = _.find @teams, name: scoreWrapper.data('team-name')
|
||||
@generateScoreLineChart(scoreWrapper.attr('id'), team.scoreHistory, team.name)
|
||||
|
||||
|
||||
generateScoreLineChart: (wrapperID, scoreHistory,teamName) =>
|
||||
margin =
|
||||
margin =
|
||||
top: 20
|
||||
right: 20
|
||||
bottom: 30
|
||||
left: 50
|
||||
|
||||
|
||||
width = 450 - margin.left - margin.right
|
||||
height = 125
|
||||
x = d3.time.scale().range([0,width])
|
||||
y = d3.scale.linear().range([height,0])
|
||||
|
||||
|
||||
xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(4).outerTickSize(0)
|
||||
yAxis = d3.svg.axis().scale(y).orient("left").ticks(4).outerTickSize(0)
|
||||
|
||||
|
||||
line = d3.svg.line().x(((d) -> x(d.date))).y((d) -> y(d.close))
|
||||
selector = "#" + wrapperID
|
||||
|
||||
|
||||
svg = d3.select(selector).append("svg")
|
||||
.attr("width", width + margin.left + margin.right)
|
||||
.attr("height", height + margin.top + margin.bottom)
|
||||
|
@ -150,12 +149,10 @@ module.exports = class MyMatchesTabView extends CocoView
|
|||
date: time
|
||||
close: d[1] * 100
|
||||
}
|
||||
|
||||
|
||||
x.domain(d3.extent(data, (d) -> d.date))
|
||||
y.domain(d3.extent(data, (d) -> d.close))
|
||||
|
||||
|
||||
|
||||
|
||||
svg.append("g")
|
||||
.attr("class", "y axis")
|
||||
.call(yAxis)
|
||||
|
@ -172,21 +169,6 @@ module.exports = class MyMatchesTabView extends CocoView
|
|||
.datum(data)
|
||||
.attr("class",lineClass)
|
||||
.attr("d",line)
|
||||
|
||||
|
||||
|
||||
|
||||
readyToRank: (session) ->
|
||||
return false unless session?.get('levelID') # If it hasn't been denormalized, then it's not ready.
|
||||
return false unless c1 = session.get('code')
|
||||
return false unless team = session.get('team')
|
||||
return true unless c2 = session.get('submittedCode')
|
||||
thangSpellArr = (s.split("/") for s in session.get('teamSpells')[team])
|
||||
for item in thangSpellArr
|
||||
thang = item[0]
|
||||
spell = item[1]
|
||||
return true if c1[thang][spell] isnt c2[thang][spell]
|
||||
return false
|
||||
|
||||
rankSession: (e) ->
|
||||
button = $(e.target).closest('.rank-button')
|
||||
|
@ -202,7 +184,6 @@ module.exports = class MyMatchesTabView extends CocoView
|
|||
@setRankingButtonText(button, 'failed')
|
||||
|
||||
ajaxData = {session: sessionID, levelID: @level.id, originalLevelID: @level.attributes.original, levelMajorVersion: @level.attributes.version.major}
|
||||
console.log "Posting game for ranking from My Matches view."
|
||||
$.ajax '/queue/scoring', {
|
||||
type: 'POST'
|
||||
data: ajaxData
|
||||
|
|
|
@ -35,7 +35,6 @@ module.exports = class ControlBarView extends View
|
|||
@session = options.session
|
||||
@level = options.level
|
||||
@playableTeams = options.playableTeams
|
||||
@ladderGame = options.ladderGame
|
||||
@spectateGame = options.spectateGame ? false
|
||||
super options
|
||||
|
||||
|
@ -55,13 +54,12 @@ module.exports = class ControlBarView extends View
|
|||
super c
|
||||
c.worldName = @worldName
|
||||
c.multiplayerEnabled = @session.get('multiplayer')
|
||||
c.ladderGame = @ladderGame
|
||||
c.ladderGame = @level.get('type') is 'ladder'
|
||||
c.spectateGame = @spectateGame
|
||||
c.homeLink = "/"
|
||||
levelID = @level.get('slug')
|
||||
if levelID in ["brawlwood", "brawlwood-tutorial", "dungeon-arena", "dungeon-arena-tutorial"]
|
||||
levelID = 'brawlwood' if levelID is 'brawlwood-tutorial'
|
||||
c.homeLink = "/play/ladder/" + levelID
|
||||
if @level.get('type') in ['ladder', 'ladder-tutorial']
|
||||
c.homeLink = '/play/ladder/' + @level.get('slug').replace /\-tutorial$/, ''
|
||||
else
|
||||
c.homeLink = '/'
|
||||
c
|
||||
|
||||
afterRender: ->
|
||||
|
|
|
@ -9,7 +9,7 @@ module.exports = class MultiplayerModal extends View
|
|||
events:
|
||||
'click textarea': 'onClickLink'
|
||||
'change #multiplayer': 'updateLinkSection'
|
||||
|
||||
'click .rank-game-button': 'onRankGame'
|
||||
|
||||
constructor: (options) ->
|
||||
super(options)
|
||||
|
@ -17,20 +17,20 @@ module.exports = class MultiplayerModal extends View
|
|||
@level = options.level
|
||||
@listenTo(@session, 'change:multiplayer', @updateLinkSection)
|
||||
@playableTeams = options.playableTeams
|
||||
@ladderGame = options.ladderGame
|
||||
console.log 'ladder game is', @ladderGame
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
c.joinLink = (document.location.href.replace(/\?.*/, '').replace('#', '') +
|
||||
'?session=' +
|
||||
@session.id)
|
||||
c.multiplayer = @session.get('multiplayer')
|
||||
c.multiplayer = @session.get 'multiplayer'
|
||||
c.team = @session.get 'team'
|
||||
c.levelSlug = @level?.get('slug')
|
||||
c.levelSlug = @level?.get 'slug'
|
||||
c.playableTeams = @playableTeams
|
||||
c.ladderGame = @ladderGame
|
||||
# For now, ladderGame will disallow multiplayer, because session code combining doesn't play nice yet.
|
||||
if @level?.get('type') is 'ladder'
|
||||
c.ladderGame = true
|
||||
c.readyToRank = @session?.readyToRank()
|
||||
c
|
||||
|
||||
afterRender: ->
|
||||
|
@ -50,5 +50,20 @@ module.exports = class MultiplayerModal extends View
|
|||
multiplayer = Boolean(@$el.find('#multiplayer').prop('checked'))
|
||||
@session.set('multiplayer', multiplayer)
|
||||
|
||||
onRankGame: (e) ->
|
||||
button = @$el.find('.rank-game-button')
|
||||
button.text($.i18n.t('play_level.victory_ranking_game', defaultValue: 'Submitting...'))
|
||||
button.prop 'disabled', true
|
||||
ajaxData = session: @session.id, levelID: @level.id, originalLevelID: @level.get('original'), levelMajorVersion: @level.get('version').major
|
||||
ladderURL = "/play/ladder/#{@level.get('slug')}#my-matches"
|
||||
goToLadder = -> Backbone.Mediator.publish 'router:navigate', route: ladderURL
|
||||
$.ajax '/queue/scoring',
|
||||
type: 'POST'
|
||||
data: ajaxData
|
||||
success: goToLadder
|
||||
failure: (response) ->
|
||||
console.error "Couldn't submit game for ranking:", response
|
||||
goToLadder()
|
||||
|
||||
destroy: ->
|
||||
super()
|
||||
|
|
|
@ -65,7 +65,6 @@ module.exports = class VictoryModal extends View
|
|||
ajaxData = session: @session.id, levelID: @level.id, originalLevelID: @level.get('original'), levelMajorVersion: @level.get('version').major
|
||||
ladderURL = "/play/ladder/#{@level.get('slug')}#my-matches"
|
||||
goToLadder = -> Backbone.Mediator.publish 'router:navigate', route: ladderURL
|
||||
console.log "Posting game for ranking from victory modal."
|
||||
$.ajax '/queue/scoring',
|
||||
type: 'POST'
|
||||
data: ajaxData
|
||||
|
@ -82,9 +81,7 @@ module.exports = class VictoryModal extends View
|
|||
c.levelName = utils.i18n @level.attributes, 'name'
|
||||
c.level = @level
|
||||
if c.level.get('type') is 'ladder'
|
||||
c1 = @session?.get('code')
|
||||
c2 = @session?.get('submittedCode')
|
||||
c.readyToRank = @session.get('levelID') and c1 and not _.isEqual(c1, c2)
|
||||
c.readyToRank = @session.readyToRank()
|
||||
if me.get 'hourOfCode'
|
||||
# Show the Hour of Code "I'm Done" tracking pixel after they played for 30 minutes
|
||||
elapsed = (new Date() - new Date(me.get('dateCreated')))
|
||||
|
|
|
@ -97,8 +97,12 @@ module.exports = class SpellView extends View
|
|||
aceCommands.push c.name
|
||||
addCommand
|
||||
name: 'run-code'
|
||||
bindKey: {win: 'Shift-Enter|Ctrl-Enter|Ctrl-S', mac: 'Shift-Enter|Command-Enter|Ctrl-Enter|Command-S|Ctrl-S'}
|
||||
bindKey: {win: 'Shift-Enter|Ctrl-Enter', mac: 'Shift-Enter|Command-Enter|Ctrl-Enter'}
|
||||
exec: -> Backbone.Mediator.publish 'tome:manual-cast', {}
|
||||
addCommand
|
||||
name: 'no-op'
|
||||
bindKey: {win: 'Ctrl-S', mac: 'Command-S|Ctrl-S'}
|
||||
exec: -> # just prevent page save call
|
||||
addCommand
|
||||
name: 'toggle-playing'
|
||||
bindKey: {win: 'Ctrl-P', mac: 'Command-P|Ctrl-P'}
|
||||
|
|
|
@ -174,7 +174,7 @@ module.exports = class PlayLevelView extends View
|
|||
@initSurface()
|
||||
@initGoalManager()
|
||||
@initScriptManager()
|
||||
@insertSubviews ladderGame: (@level.get('type') is "ladder")
|
||||
@insertSubviews()
|
||||
@initVolume()
|
||||
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
|
||||
@originalSessionState = $.extend(true, {}, @session.get('state'))
|
||||
|
@ -238,15 +238,15 @@ module.exports = class PlayLevelView extends View
|
|||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
ctx.fillText("Loaded #{@modelsLoaded} thingies",50,50)
|
||||
|
||||
insertSubviews: (subviewOptions) ->
|
||||
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, thangs: @world.thangs, supermodel: @supermodel, ladderGame: subviewOptions.ladderGame
|
||||
insertSubviews: ->
|
||||
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, thangs: @world.thangs, supermodel: @supermodel
|
||||
@insertSubView new PlaybackView {}
|
||||
@insertSubView new GoalsView {}
|
||||
@insertSubView new GoldView {}
|
||||
@insertSubView new HUDView {}
|
||||
@insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session
|
||||
worldName = utils.i18n @level.attributes, 'name'
|
||||
@controlBar = @insertSubView new ControlBarView {worldName: worldName, session: @session, level: @level, supermodel: @supermodel, playableTeams: @world.playableTeams, ladderGame: subviewOptions.ladderGame}
|
||||
@controlBar = @insertSubView new ControlBarView {worldName: worldName, session: @session, level: @level, supermodel: @supermodel, playableTeams: @world.playableTeams}
|
||||
#Backbone.Mediator.publish('level-set-debug', debug: true) if me.displayName() is 'Nick!'
|
||||
|
||||
afterInsert: ->
|
||||
|
|
|
@ -161,7 +161,7 @@ module.exports = class SpectateLevelView extends View
|
|||
@initSurface()
|
||||
@initGoalManager()
|
||||
@initScriptManager()
|
||||
@insertSubviews ladderGame: @otherSession?
|
||||
@insertSubviews()
|
||||
@initVolume()
|
||||
|
||||
@originalSessionState = $.extend(true, {}, @session.get('state'))
|
||||
|
@ -229,8 +229,8 @@ module.exports = class SpectateLevelView extends View
|
|||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
ctx.fillText("Loaded #{@modelsLoaded} thingies",50,50)
|
||||
|
||||
insertSubviews: (subviewOptions) ->
|
||||
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, thangs: @world.thangs, supermodel: @supermodel, ladderGame: subviewOptions.ladderGame
|
||||
insertSubviews: ->
|
||||
@insertSubView @tome = new TomeView levelID: @levelID, session: @session, thangs: @world.thangs, supermodel: @supermodel
|
||||
@insertSubView new PlaybackView {}
|
||||
|
||||
@insertSubView new GoldView {}
|
||||
|
|
|
@ -8,5 +8,5 @@ current_directory = os.path.dirname(os.path.realpath(sys.argv[0]))
|
|||
coco_path = os.getenv("COCO_DIR",os.path.join(current_directory,os.pardir))
|
||||
nodemon_path = coco_path + os.sep + "node_modules" + os.sep + ".bin" + os.sep + "nodemon"
|
||||
|
||||
call(nodemon_path + " . --ext \".coffee|.js\" --watch server --watch app.js --watch server_config.js --watch server_setup.coffee",shell=True,cwd=coco_path)
|
||||
call(nodemon_path + " . --ext \".coffee|.js\" --watch server --watch server_config.js --watch server_setup.coffee --watch app" + os.sep + "schemas",shell=True,cwd=coco_path)
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
"mongoose": "3.8.x",
|
||||
"mongoose-text-search": "~0.0.2",
|
||||
"request": "2.12.x",
|
||||
"tv4": "1.0.x",
|
||||
"tv4": "~1.0.16",
|
||||
"lodash": "~2.0.0",
|
||||
"underscore.string": "2.3.x",
|
||||
"async": "0.2.x",
|
||||
|
@ -92,7 +92,8 @@
|
|||
"karma-phantomjs-launcher": "~0.1.1",
|
||||
"karma": "~0.10.9",
|
||||
"karma-coverage": "~0.1.4",
|
||||
"compressible": "~1.0.1"
|
||||
"compressible": "~1.0.1",
|
||||
"jasmine-spec-reporter":"~0.3.0"
|
||||
},
|
||||
"license": "MIT for the code, and CC-BY for the art and music",
|
||||
"private": true,
|
||||
|
|
|
@ -15,7 +15,7 @@ module.exports.connect = () ->
|
|||
|
||||
|
||||
module.exports.generateMongoConnectionString = ->
|
||||
if config.mongo.mongoose_replica_string
|
||||
if not testing and config.mongo.mongoose_replica_string
|
||||
address = config.mongo.mongoose_replica_string
|
||||
else
|
||||
dbName = config.mongo.db
|
||||
|
@ -25,4 +25,4 @@ module.exports.generateMongoConnectionString = ->
|
|||
address = config.mongo.username + ":" + config.mongo.password + "@" + address
|
||||
address = "mongodb://#{address}/#{dbName}"
|
||||
|
||||
return address
|
||||
return address
|
||||
|
|
|
@ -76,18 +76,18 @@ handleLadderUpdate = (req, res) ->
|
|||
sendLadderUpdateEmail result, now, daysAgo for result in results
|
||||
|
||||
sendLadderUpdateEmail = (session, now, daysAgo) ->
|
||||
User.findOne({_id: session.creator}).select("name email firstName lastName emailSubscriptions emails preferredLanguage").lean().exec (err, user) ->
|
||||
User.findOne({_id: session.creator}).select("name email firstName lastName emailSubscriptions emails preferredLanguage").exec (err, user) ->
|
||||
if err
|
||||
log.error "Couldn't find user for #{session.creator} from session #{session._id}"
|
||||
return
|
||||
allowNotes = user.isEmailSubscriptionEnabled 'anyNotes'
|
||||
unless user.email and allowNotes and not session.unsubscribed
|
||||
log.info "Not sending email to #{user.email} #{user.name} because they only want emails about #{user.emailSubscriptions}, #{user.emails} - session unsubscribed: #{session.unsubscribed}"
|
||||
unless user.get('email') and allowNotes and not session.unsubscribed
|
||||
log.info "Not sending email to #{user.get('email')} #{user.get('name')} because they only want emails about #{user.get('emailSubscriptions')}, #{user.get('emails')} - session unsubscribed: #{session.unsubscribed}"
|
||||
return
|
||||
unless session.levelName
|
||||
log.info "Not sending email to #{user.email} #{user.name} because the session had no levelName in it."
|
||||
log.info "Not sending email to #{user.get('email')} #{user.get('name')} because the session had no levelName in it."
|
||||
return
|
||||
name = if user.firstName and user.lastName then "#{user.firstName}" else user.name
|
||||
name = if user.get('firstName') and user.get('lastName') then "#{user.get('firstName')}" else user.get('name')
|
||||
name = "Wizard" if not name or name is "Anoner"
|
||||
|
||||
# Fetch the most recent defeat and victory, if there are any.
|
||||
|
@ -108,7 +108,7 @@ sendLadderUpdateEmail = (session, now, daysAgo) ->
|
|||
context =
|
||||
email_id: sendwithus.templates.ladder_update_email
|
||||
recipient:
|
||||
address: if DEBUGGING then 'nick@codecombat.com' else user.email
|
||||
address: if DEBUGGING then 'nick@codecombat.com' else user.get('email')
|
||||
name: name
|
||||
email_data:
|
||||
name: name
|
||||
|
|
|
@ -105,11 +105,12 @@ UserHandler = class UserHandler extends Handler
|
|||
(req, user, callback) ->
|
||||
return callback(null, req, user) unless req.body.name
|
||||
nameLower = req.body.name?.toLowerCase()
|
||||
return callback(null, req, user) if nameLower is user.get('nameLower')
|
||||
User.findOne({nameLower:nameLower}).exec (err, otherUser) ->
|
||||
# return callback(null, req, user) if nameLower is user.get('nameLower')
|
||||
User.findOne({nameLower:nameLower,anonymous:false}).exec (err, otherUser) ->
|
||||
log.error "Database error setting user name: #{err}" if err
|
||||
return callback(res:'Database error.', code:500) if err
|
||||
r = {message:'is already used by another account', property:'name'}
|
||||
console.log 'Another user exists' if otherUser
|
||||
return callback({res:r, code:409}) if otherUser
|
||||
user.set('name', req.body.name)
|
||||
callback(null, req, user)
|
||||
|
@ -127,7 +128,7 @@ UserHandler = class UserHandler extends Handler
|
|||
@getPropertiesFromMultipleDocuments res, User, properties, ids
|
||||
|
||||
nameToID: (req, res, name) ->
|
||||
User.findOne({nameLower:name.toLowerCase()}).exec (err, otherUser) ->
|
||||
User.findOne({nameLower:name.toLowerCase(),anonymous:false}).exec (err, otherUser) ->
|
||||
res.send(if otherUser then otherUser._id else JSON.stringify(''))
|
||||
res.end()
|
||||
|
||||
|
|
|
@ -3,7 +3,12 @@
|
|||
|
||||
console.log 'IT BEGINS'
|
||||
|
||||
|
||||
require('jasmine-spec-reporter')
|
||||
jasmine.getEnv().reporter.subReporters_ = []
|
||||
jasmine.getEnv().addReporter(new jasmine.SpecReporter({
|
||||
displaySuccessfulSpec: true,
|
||||
displayFailedSpec: true
|
||||
}))
|
||||
GLOBAL._ = require('lodash')
|
||||
_.str = require('underscore.string')
|
||||
_.mixin(_.str.exports())
|
||||
|
|
|
@ -44,6 +44,20 @@ describe 'User.updateMailChimp', ->
|
|||
|
||||
describe 'POST /db/user', ->
|
||||
|
||||
createAnonNameUser = (done)->
|
||||
request.post getURL('/auth/logout'), ->
|
||||
request.get getURL('/auth/whoami'), ->
|
||||
req = request.post(getURL('/db/user'), (err, response) ->
|
||||
expect(response.statusCode).toBe(200)
|
||||
request.get getURL('/auth/whoami'), (request, response, body) ->
|
||||
res = JSON.parse(response.body)
|
||||
expect(res.anonymous).toBeTruthy()
|
||||
expect(res.name).toEqual('Jim')
|
||||
done()
|
||||
)
|
||||
form = req.form()
|
||||
form.append('name', 'Jim')
|
||||
|
||||
it 'preparing test : clears the db first', (done) ->
|
||||
clearModels [User], (err) ->
|
||||
throw err if err
|
||||
|
@ -90,6 +104,36 @@ describe 'POST /db/user', ->
|
|||
expect(user.passwordHash).toBeUndefined()
|
||||
done()
|
||||
|
||||
it 'should allow setting anonymous user name', (done) ->
|
||||
createAnonNameUser(done)
|
||||
|
||||
it 'should allow multiple anonymous users with same name', (done) ->
|
||||
createAnonNameUser(done)
|
||||
|
||||
|
||||
it 'should not allow setting existing user name to anonymous user', (done) ->
|
||||
|
||||
createAnonUser = ->
|
||||
request.post getURL('/auth/logout'), ->
|
||||
request.get getURL('/auth/whoami'), ->
|
||||
req = request.post(getURL('/db/user'), (err, response) ->
|
||||
expect(response.statusCode).toBe(409)
|
||||
done()
|
||||
)
|
||||
form = req.form()
|
||||
form.append('name', 'Jim')
|
||||
|
||||
req = request.post(getURL('/db/user'), (err,response,body) ->
|
||||
expect(response.statusCode).toBe(200)
|
||||
request.get getURL('/auth/whoami'), (request, response, body) ->
|
||||
res = JSON.parse(response.body)
|
||||
expect(res.anonymous).toBeFalsy()
|
||||
createAnonUser()
|
||||
)
|
||||
form = req.form()
|
||||
form.append('email', 'new@user.com')
|
||||
form.append('password', 'new')
|
||||
|
||||
|
||||
describe 'PUT /db/user', ->
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue