Merge branch 'master' of https://github.com/codecombat/codecombat
BIN
app/assets/images/jquery.minicolors.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
app/assets/images/pages/play/ladder/humans_ladder_easy.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
app/assets/images/pages/play/ladder/humans_ladder_hard.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
app/assets/images/pages/play/ladder/humans_ladder_medium.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
app/assets/images/pages/play/ladder/humans_ladder_tutorial.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
app/assets/images/pages/play/ladder/ogres_ladder_easy.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
app/assets/images/pages/play/ladder/ogres_ladder_hard.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
app/assets/images/pages/play/ladder/ogres_ladder_medium.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
app/assets/images/pages/play/ladder/ogres_ladder_tutorial.png
Normal file
After Width: | Height: | Size: 26 KiB |
|
@ -19,7 +19,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
|||
'editor/:model(/:slug_or_id)(/:subview)': 'editorModelView'
|
||||
|
||||
# Experimenting with direct links
|
||||
'play/ladder/:levelID/team/:team': go('play/ladder/team_view')
|
||||
# 'play/ladder/:levelID/team/:team': go('play/ladder/team_view')
|
||||
|
||||
# db and file urls call the server directly
|
||||
'db/*path': 'routeToServer'
|
||||
|
|
|
@ -42,6 +42,7 @@ module.exports = class Camera extends CocoClass
|
|||
'level:restarted': 'onLevelRestarted'
|
||||
'sprite:mouse-down': 'onMouseDown'
|
||||
'sprite:dragged': 'onMouseDragged'
|
||||
'camera-zoom-to': 'onZoomTo'
|
||||
|
||||
# TODO: Fix tests to not use mainLayer
|
||||
constructor: (@canvasWidth, @canvasHeight, angle=Math.asin(0.75), hFOV=d2r(30)) ->
|
||||
|
@ -169,7 +170,7 @@ module.exports = class Camera extends CocoClass
|
|||
onMouseDown: (e) ->
|
||||
return if @dragDisabled
|
||||
@lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
|
||||
|
||||
|
||||
onMouseDragged: (e) ->
|
||||
return if @dragDisabled
|
||||
target = @boundTarget(@target, @zoom)
|
||||
|
@ -180,7 +181,7 @@ module.exports = class Camera extends CocoClass
|
|||
@zoomTo newPos, @zoom, 0
|
||||
@lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
|
||||
Backbone.Mediator.publish 'camera:dragged'
|
||||
|
||||
|
||||
onLevelRestarted: ->
|
||||
@setBounds(@firstBounds, false)
|
||||
|
||||
|
@ -220,7 +221,7 @@ module.exports = class Camera extends CocoClass
|
|||
newTarget ?= {x:0, y:0}
|
||||
newTarget = (@newTarget or @target) if @locked
|
||||
newZoom = Math.min((Math.max @minZoom, newZoom), MAX_ZOOM)
|
||||
|
||||
|
||||
thangType = @target?.sprite?.thangType
|
||||
if thangType
|
||||
@offset = _.clone(thangType.get('positions')?.torso or {x: 0, y:0})
|
||||
|
@ -229,7 +230,7 @@ module.exports = class Camera extends CocoClass
|
|||
@offset.y *= scale
|
||||
else
|
||||
@offset = {x: 0, y:0}
|
||||
|
||||
|
||||
return if @zoom is newZoom and newTarget is newTarget.x and newTarget.y is newTarget.y
|
||||
|
||||
@finishTween(true)
|
||||
|
@ -247,7 +248,7 @@ module.exports = class Camera extends CocoClass
|
|||
@target = newTarget
|
||||
@zoom = newZoom
|
||||
@updateZoom true
|
||||
|
||||
|
||||
focusedOnSprite: ->
|
||||
return @target?.name
|
||||
|
||||
|
@ -308,3 +309,6 @@ module.exports = class Camera extends CocoClass
|
|||
createjs.Tween.removeTweens @
|
||||
@finishTween = null
|
||||
super()
|
||||
|
||||
onZoomTo: (pos, time) ->
|
||||
@zoomTo(@worldToSurface(pos), @zoom, time)
|
||||
|
|
|
@ -22,6 +22,12 @@ module.exports = class WizardSprite extends IndieSprite
|
|||
'echo-self-wizard-sprite': 'onEchoSelfWizardSprite'
|
||||
'echo-all-wizard-sprites': 'onEchoAllWizardSprites'
|
||||
|
||||
shortcuts:
|
||||
'up': 'onMoveKey'
|
||||
'down': 'onMoveKey'
|
||||
'left': 'onMoveKey'
|
||||
'right': 'onMoveKey'
|
||||
|
||||
constructor: (thangType, options) ->
|
||||
if options?.isSelf
|
||||
options.colorConfig = _.cloneDeep(me.get('wizard')?.colorConfig) or {}
|
||||
|
@ -102,7 +108,7 @@ module.exports = class WizardSprite extends IndieSprite
|
|||
defaultPos: -> x: 35, y: 24, z: @thang.depth / 2 + @thang.bobHeight
|
||||
move: (pos, duration) -> @setTarget(pos, duration)
|
||||
|
||||
setTarget: (newTarget, duration) ->
|
||||
setTarget: (newTarget, duration, isLinear=false) ->
|
||||
# ignore targets you're already heading for
|
||||
targetPos = @getPosFromTarget(newTarget)
|
||||
return if @targetPos and @targetPos.x is targetPos.x and @targetPos.y is targetPos.y
|
||||
|
@ -115,7 +121,7 @@ module.exports = class WizardSprite extends IndieSprite
|
|||
@shoveOtherWizards(true) if @targetSprite
|
||||
@targetSprite = if isSprite then newTarget else null
|
||||
@targetPos = targetPos
|
||||
@beginMoveTween(duration)
|
||||
@beginMoveTween(duration, isLinear)
|
||||
@shoveOtherWizards()
|
||||
Backbone.Mediator.publish('self-wizard:target-changed', {sender:@}) if @isSelf
|
||||
|
||||
|
@ -127,7 +133,7 @@ module.exports = class WizardSprite extends IndieSprite
|
|||
return target if target.x?
|
||||
return target.thang.pos
|
||||
|
||||
beginMoveTween: (duration=1000) ->
|
||||
beginMoveTween: (duration=1000, isLinear=false) ->
|
||||
# clear the old tween
|
||||
createjs.Tween.removeTweens(@)
|
||||
|
||||
|
@ -140,8 +146,11 @@ module.exports = class WizardSprite extends IndieSprite
|
|||
@updatePosition()
|
||||
@endMoveTween()
|
||||
return
|
||||
if isLinear
|
||||
ease = createjs.Ease.linear
|
||||
else
|
||||
ease = createjs.Ease.getPowInOut(3.0)
|
||||
|
||||
ease = createjs.Ease.getPowInOut(3.0)
|
||||
createjs.Tween
|
||||
.get(@)
|
||||
.to({tweenPercentage:0.0}, duration, ease)
|
||||
|
@ -225,3 +234,22 @@ module.exports = class WizardSprite extends IndieSprite
|
|||
|
||||
updateMarks: ->
|
||||
super() if @displayObject.visible # not if we hid the wiz
|
||||
|
||||
|
||||
onMoveKey: (e) ->
|
||||
return unless @isSelf
|
||||
e?.preventDefault()
|
||||
yMovement = 0
|
||||
xMovement = 0
|
||||
yMovement += 2 if key.isPressed('up')
|
||||
yMovement -= 2 if key.isPressed('down')
|
||||
xMovement += 2 if key.isPressed('right')
|
||||
xMovement -= 2 if key.isPressed('left')
|
||||
@moveWizard xMovement, yMovement
|
||||
|
||||
moveWizard: (x, y) ->
|
||||
interval = 500
|
||||
position = {x: @targetPos.x + x, y: @targetPos.y + y}
|
||||
@setTarget(position, interval, true)
|
||||
@updatePosition()
|
||||
Backbone.Mediator.publish 'camera-zoom-to', position, interval
|
||||
|
|
|
@ -44,6 +44,7 @@ module.exports.thangNames = thangNames =
|
|||
"Huburt"
|
||||
"Sterling"
|
||||
"Alistair"
|
||||
"Cid"
|
||||
"Remy"
|
||||
"Stormy"
|
||||
"Halle"
|
||||
|
|
|
@ -130,6 +130,7 @@ module.exports = nativeDescription: "English", englishDescription: "English", tr
|
|||
new_password_verify: "Verify"
|
||||
email_subscriptions: "Email Subscriptions"
|
||||
email_announcements: "Announcements"
|
||||
email_notifications: "Notifications"
|
||||
email_notifications_description: "Get periodic notifications for your account."
|
||||
email_announcements_description: "Get emails on the latest news and developments at CodeCombat."
|
||||
contributor_emails: "Contributor Class Emails"
|
||||
|
@ -246,6 +247,12 @@ module.exports = nativeDescription: "English", englishDescription: "English", tr
|
|||
create_system_title: "Create New System"
|
||||
new_component_title: "Create New Component"
|
||||
new_component_field_system: "System"
|
||||
new_article_title: "Create a New Article"
|
||||
new_thang_title: "Create a New Thang Type"
|
||||
new_level_title: "Create a New Level"
|
||||
article_search_title: "Search Articles Here"
|
||||
thang_search_title: "Search Thang Types Here"
|
||||
level_search_title: "Search Levels Here"
|
||||
|
||||
article:
|
||||
edit_btn_preview: "Preview"
|
||||
|
|
|
@ -8,7 +8,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
delay_1_sec: "1 seconde"
|
||||
delay_3_sec: "3 secondes"
|
||||
delay_5_sec: "5 secondes"
|
||||
manual: "Handboek"
|
||||
manual: "Handmatig"
|
||||
fork: "Fork"
|
||||
play: "Spelen"
|
||||
|
||||
|
@ -116,7 +116,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
account_settings:
|
||||
title: "Account Instellingen"
|
||||
not_logged_in: "Log in of maak een account om je instellingen aan te passen."
|
||||
autosave: "Aanpassingen Worden Automatisch Opgeslagen"
|
||||
autosave: "Aanpassingen Automatisch Opgeslagen"
|
||||
me_tab: "Ik"
|
||||
picture_tab: "Afbeelding"
|
||||
wizard_tab: "Tovenaar"
|
||||
|
@ -130,6 +130,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
new_password_verify: "Verifieer"
|
||||
email_subscriptions: "E-mail Abonnementen"
|
||||
email_announcements: "Aankondigingen"
|
||||
email_notifications: "Notificaties"
|
||||
email_notifications_description: "Krijg periodieke meldingen voor jouw account."
|
||||
email_announcements_description: "Verkrijg emails over het laatste nieuws en de ontwikkelingen bij CodeCombat."
|
||||
contributor_emails: "Medewerker Klasse emails"
|
||||
|
@ -215,11 +216,11 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
main_title: "CodeCombat Editors"
|
||||
main_description: "Maak je eigen levels, campagnes, eenheden en leermateriaal. Wij bieden alle programma's aan die u nodig heeft!"
|
||||
article_title: "Artikel Editor"
|
||||
article_description: "Schrijf artikels dat spelers een overzicht geven over programmeer concepten die kunnen gebruikt worden over een variëteit van levels en campagnes."
|
||||
article_description: "Schrijf artikels die spelers een overzicht geven over programmeer concepten die kunnen gebruikt worden over een variëteit van levels en campagnes."
|
||||
thang_title: "Thang Editor"
|
||||
thang_description: "Maak eenheden, beschrijf hun default logica, graphics en audio. Momenteel is enkel het importeren van vector graphics geëxporteerd in Flash ondersteund."
|
||||
thang_description: "Maak eenheden, beschrijf hun standaard logica, graphics en audio. Momenteel is enkel het importeren van vector graphics geëxporteerd in Flash ondersteund."
|
||||
level_title: "Level Editor"
|
||||
level_description: "Bevat het programma om te programmeren, audio te uploaden en aangepaste logica om alle soorten levels te maken. Het is alles wat wijzelf ook gebruiken!"
|
||||
level_description: "Bevat het programma om te programmeren, audio te uploaden en aangepaste logica te creëren om alle soorten levels te maken. Het is alles wat wijzelf ook gebruiken!"
|
||||
security_notice: "Veel belangrijke elementen in deze editors zijn momenteel niet actief. Met dat wij de veiligheid van deze systemen verbeteren, zullen ook deze elementen beschikbaar worden. Indien u deze elementen al eerder wil gebruiken, "
|
||||
contact_us: "contacteer ons!"
|
||||
hipchat_prefix: "Je kan ons ook vinden in ons"
|
||||
|
@ -246,6 +247,12 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
|
|||
create_system_title: "Maak een nieuw Systeem aan"
|
||||
new_component_title: "Maak een nieuw Component aan"
|
||||
new_component_field_system: "Systeem"
|
||||
new_article_title: "Maak een Nieuw Artikel"
|
||||
new_thang_title: "Maak een Nieuw Thang Type"
|
||||
new_level_title: "Maak een Nieuw Level"
|
||||
article_search_title: "Zoek Artikels Hier"
|
||||
thang_search_title: "Zoek Thang Types Hier"
|
||||
level_search_title: "Zoek Levels Hier"
|
||||
|
||||
article:
|
||||
edit_btn_preview: "Voorbeeld"
|
||||
|
|
|
@ -201,15 +201,15 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
|||
hud_continue: "Продолжить (нажмите Shift+Пробел)"
|
||||
spell_saved: "Заклинание сохранено"
|
||||
|
||||
# admin:
|
||||
# av_title: "Admin Views"
|
||||
# av_entities_sub_title: "Entities"
|
||||
# av_entities_users_url: "Users"
|
||||
# av_entities_active_instances_url: "Active Instances"
|
||||
# av_other_sub_title: "Other"
|
||||
# av_other_debug_base_url: "Base (for debugging base.jade)"
|
||||
# u_title: "User List"
|
||||
# lg_title: "Latest Games"
|
||||
admin:
|
||||
av_title: "Админ панель"
|
||||
av_entities_sub_title: "Сущности"
|
||||
av_entities_users_url: "Пользователи"
|
||||
av_entities_active_instances_url: "Активные экземпляры"
|
||||
av_other_sub_title: "Другое"
|
||||
av_other_debug_base_url: "База (для отладки base.jade)"
|
||||
u_title: "Список пользователей"
|
||||
lg_title: "Последние игры"
|
||||
|
||||
editor:
|
||||
main_title: "Редакторы CodeCombat"
|
||||
|
@ -224,7 +224,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
|||
contact_us: "свяжитесь с нами!"
|
||||
hipchat_prefix: "Также вы можете найти нас в нашей"
|
||||
hipchat_url: "комнате HipChat."
|
||||
# level_some_options: "Some Options?"
|
||||
level_some_options: "Ещё опции"
|
||||
level_tab_thangs: "Объекты"
|
||||
level_tab_scripts: "Скрипты"
|
||||
level_tab_settings: "Настройки"
|
||||
|
@ -254,7 +254,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
|||
general:
|
||||
and: "и"
|
||||
name: "Имя"
|
||||
# body: "Body"
|
||||
body: "Содержание"
|
||||
version: "Версия"
|
||||
commit_msg: "Сопроводительное сообщение"
|
||||
version_history_for: "История версий для: "
|
||||
|
@ -418,22 +418,22 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
|
|||
diplomat_join_suf_github: ", отредактируйте его онлайн и отправьте запрос на включение изменений. Кроме того, установите флажок ниже, чтобы быть в курсе новых разработок интернационализации!"
|
||||
more_about_diplomat: "Узнать больше о том, как стать Дипломатом"
|
||||
diplomat_subscribe_desc: "Получать email-ы о i18n разработках и уровнях для перевода."
|
||||
# ambassador_summary: "Мы пытаемся создать сообщество, и каждое сообщество нуждается в службе поддержки, когда есть проблемы. У нас есть чаты, электронная почта и социальные сети, чтобы наши пользователи могли познакомиться с игрой. Если вы хотите помочь людям втянуться, получать удовольствие и учиться программированию, этот класс для вас." # Not done yet
|
||||
# ambassador_introduction: "This is a community we're building, and you are the connections. We've got Olark chats, emails, and social networks with lots of people to talk with and help get acquainted with the game and learn from. If you want to help people get involved and have fun, and get a good feel of the pulse of CodeCombat and where we're going, then this class might be for you."
|
||||
# ambassador_attribute_1: "Communication skills. Be able to identify the problems players are having and help them solve them. Also, keep the rest of us informed about what players are saying, what they like and don't like and want more of!"
|
||||
# ambassador_join_desc: "tell us a little about yourself, what you've done and what you'd be interested in doing. We'll go from there!"
|
||||
# ambassador_join_note_strong: "Note"
|
||||
# ambassador_join_note_desc: "One of our top priorities is to build multiplayer where players having difficulty solving levels can summon higher level wizards to help them. This will be a great way for ambassadors to do their thing. We'll keep you posted!"
|
||||
# more_about_ambassador: "Узнать больше о том, как стать Ambassador" # Not done yet
|
||||
# ambassador_subscribe_desc: "Get emails on support updates and multiplayer developments."
|
||||
# counselor_summary: "None of the above roles fit what you are interested in? Do not worry, we are on the lookout for anybody who wants a hand in the development of CodeCombat! If you are interested in teaching, game development, open source management, or anything else that you think will be relevant to us, then this class is for you."
|
||||
# counselor_introduction_1: "Do you have life experience? A different perspective on things that can help us decide how to shape CodeCombat? Of all these roles, this will probably take the least time, but individually you may make the most difference. We're on the lookout for wisened sages, particularly in areas like: teaching, game development, open source project management, technical recruiting, entrepreneurship, or design."
|
||||
# counselor_introduction_2: "Or really anything that is relevant to the development of CodeCombat. If you have knowledge and want to share it to help grow this project, then this class might be for you."
|
||||
# counselor_attribute_1: "Experience, in any of the areas above or something you think might be helpful."
|
||||
# counselor_attribute_2: "A little bit of free time!"
|
||||
# counselor_join_desc: "tell us a little about yourself, what you've done and what you'd be interested in doing. We'll put you in our contact list and be in touch when we could use advice (not too often)."
|
||||
# more_about_counselor: "Узнать больше о том, как стать Counselor" # Not done yet
|
||||
# changes_auto_save: "Changes are saved automatically when you toggle checkboxes."
|
||||
ambassador_summary: "Мы пытаемся создать сообщество, и каждое сообщество нуждается в службе поддержки, когда есть проблемы. У нас есть чаты, электронная почта и социальные сети, чтобы наши пользователи могли познакомиться с игрой. Если вы хотите помочь людям втянуться, получать удовольствие и учиться программированию, этот класс для вас."
|
||||
ambassador_introduction: "Это сообщество, которое мы создаём, и вы соединяете. У нас есть Olark чаты, электронная почта и социальные сети с уймой людей, с которыми нужно поговорить, помочь в ознакомлении с игрой и обучении из неё. Если вы хотите помочь людям втянуться, получать удовольствие, наслаждаться и и куда мы идём, этот класс для вас."
|
||||
ambassador_attribute_1: "Навыки общения. Уметь определять проблемы игроков и помогать решить их. Кроме того, держите всех нас в курсе о том, что игроки говорят, что им нравится, не нравится и чего хотят больше!"
|
||||
ambassador_join_desc: "расскажите нам немного о себе, чем вы занимались и чем хотели бы заниматься. Отсюда и начнём!"
|
||||
ambassador_join_note_strong: "Примечание"
|
||||
ambassador_join_note_desc: "Одним из наших главных приоритетов является создание мультиплеера, где игроки столкнутся с труднорешаемыми уровнями и могут призвать более высокоуровневых волшебников для помощи. Это будет отличным способом для послов делать свое дело. Мы будем держать вас в курсе!"
|
||||
more_about_ambassador: "Узнать больше о том, как стать Послом"
|
||||
ambassador_subscribe_desc: "Получать email-ы о разработке мультиплеера и обновлениях в системе поддержки."
|
||||
counselor_summary: "Ни одна из вышеупомянутых ролей не соответствует тому, в чём вы заинтересованы? Не волнуйтесь, мы в поисках тех, кто хочет приложить руку к разработке CodeCombat! Если вы заинтересованы в обучении, разработке игр, управлением проектами с открытым исходным кодом, или в чём-нибудь ещё, что, как вы думаете, будет актуально для нас, то этот класс для вас."
|
||||
counselor_introduction_1: "У вас есть жизненный опыт? Другая точка зрения на вещи, которые могут помочь нам решить, как формировать CodeCombat? Из всех этих ролей, эта, возможно, займёт меньше всего времени, но по отдельности, вы можете сделать наибольшие изменения. Мы в поисках морщинистых мудрецов, особенно в таких областях, как: обучение, разработка игр, управление проектами с открытым исходным кодом, технической рекрутинг, предпринимательство или дизайн."
|
||||
counselor_introduction_2: "Или действительно всё, что имеет отношение к развитию CodeCombat. Если у вас есть знания и вы хотите поделиться ими, чтобы помочь вырастить этот проект, то этот класс для вас."
|
||||
counselor_attribute_1: "Опыт, в любой из областей выше, или в том, что, как вы думаете, может быть полезным."
|
||||
counselor_attribute_2: "Немного свободного времени!"
|
||||
counselor_join_desc: "расскажите нам немного о себе, чем вы занимались и чем хотели бы заниматься. Мы поместим вас в наш список контактов и выйдем на связь, когда нам понадобится совет(не слишком часто)."
|
||||
more_about_counselor: "Узнать больше о том, как стать Советником"
|
||||
changes_auto_save: "Изменения сохраняются автоматически при переключении флажков."
|
||||
diligent_scribes: "Наши старательные Писари:"
|
||||
powerful_archmages: "Наши могущественные Архимаги:"
|
||||
creative_artisans: "Наши творческие Ремесленники:"
|
||||
|
|
|
@ -117,7 +117,6 @@ module.exports = class Level extends CocoModel
|
|||
else if path is '/'
|
||||
# We also we need to make sure we grab the Wizard ThangType and the Marks. Hackitrooooid!
|
||||
for [type, original] in [
|
||||
["Wizard", "52a00d55cf1818f2be00000b"]
|
||||
["Highlight", "529f8fdbdacd325127000003"]
|
||||
["Selection", "52aa5f7520fccb0000000002"]
|
||||
["Target", "52b32ad97385ec3d03000001"]
|
||||
|
@ -126,4 +125,5 @@ module.exports = class Level extends CocoModel
|
|||
link = "/db/thang_type/#{original}/version"
|
||||
model = CocoModel.getOrMakeModelFromLink link, shouldLoadProjection
|
||||
models.push model if model
|
||||
models.push ThangType.loadUniversalWizard()
|
||||
models
|
||||
|
|
|
@ -172,7 +172,10 @@ module.exports = class ThangType extends CocoModel
|
|||
key = spriteOptionsOrKey
|
||||
key = if _.isString(key) then key else @spriteSheetKey(@fillOptions(key))
|
||||
spriteSheet = @spriteSheets[key]
|
||||
spriteSheet ?= @buildSpriteSheet({portraitOnly:true})
|
||||
if not spriteSheet
|
||||
options = if _.isPlainObject spriteOptionsOrKey then spriteOptionsOrKey else {}
|
||||
options.portraitOnly = true
|
||||
spriteSheet = @buildSpriteSheet(options)
|
||||
return unless spriteSheet
|
||||
canvas = $("<canvas width='#{size}' height='#{size}'></canvas>")
|
||||
stage = new createjs.Stage(canvas[0])
|
||||
|
@ -210,3 +213,12 @@ module.exports = class ThangType extends CocoModel
|
|||
|
||||
onFileUploaded: =>
|
||||
console.log 'Image uploaded'
|
||||
|
||||
@loadUniversalWizard: ->
|
||||
return @wizardType if @wizardType
|
||||
wizOriginal = "52a00d55cf1818f2be00000b"
|
||||
url = "/db/thang_type/#{wizOriginal}/version"
|
||||
@wizardType = new module.exports()
|
||||
@wizardType.url = -> url
|
||||
@wizardType.fetch()
|
||||
@wizardType
|
9
app/styles/not_found.sass
Normal file
|
@ -0,0 +1,9 @@
|
|||
@import "bootstrap/mixins"
|
||||
@import "bootstrap/variables"
|
||||
|
||||
#not-found-view
|
||||
|
||||
.not-found-image
|
||||
display: block
|
||||
margin-left: auto
|
||||
margin-right: auto
|
|
@ -1,4 +1,10 @@
|
|||
#ladder-view
|
||||
h1
|
||||
text-align: center
|
||||
|
||||
.tab-pane
|
||||
margin-top: 10px
|
||||
|
||||
.score-cell
|
||||
width: 50px
|
||||
|
||||
|
@ -6,14 +12,6 @@
|
|||
margin-bottom: 10px
|
||||
background-image: none
|
||||
|
||||
.intro-button
|
||||
width: 45%
|
||||
margin: 0 2.5%
|
||||
|
||||
#simulation-status-text
|
||||
display: inline
|
||||
margin-left: 10px
|
||||
|
||||
.name-col-cell
|
||||
max-width: 300px
|
||||
text-overflow: ellipsis
|
||||
|
|
141
app/styles/play/ladder/play_modal.sass
Normal file
|
@ -0,0 +1,141 @@
|
|||
#ladder-play-modal
|
||||
.tutorial-suggestion
|
||||
text-align: center
|
||||
font-size: 18px
|
||||
|
||||
.play-option
|
||||
margin-bottom: 15px
|
||||
width: 100%
|
||||
height: 100px
|
||||
overflow: hidden
|
||||
background: white
|
||||
border: 1px solid #333
|
||||
position: relative
|
||||
|
||||
-webkit-transition: opacity 0.3s ease-in-out
|
||||
-moz-transition: opacity 0.3s ease-in-out
|
||||
-ms-transition: opacity 0.3s ease-in-out
|
||||
-o-transition: opacity 0.3s ease-in-out
|
||||
transition: opacity 0.3s ease-in-out
|
||||
|
||||
opacity: 0.4
|
||||
|
||||
border-radius: 5px
|
||||
.only-one
|
||||
-webkit-transition: opacity 0.3s ease-in-out
|
||||
-moz-transition: opacity 0.3s ease-in-out
|
||||
-ms-transition: opacity 0.3s ease-in-out
|
||||
-o-transition: opacity 0.3s ease-in-out
|
||||
transition: opacity 0.3s ease-in-out
|
||||
opacity: 0
|
||||
|
||||
.play-option:hover
|
||||
opacity: 1
|
||||
.only-one
|
||||
opacity: 1
|
||||
|
||||
.my-icon
|
||||
position: relative
|
||||
left: 0
|
||||
top: -10px
|
||||
z-index: 1
|
||||
|
||||
.my-team-icon
|
||||
height: 60px
|
||||
position: relative
|
||||
top: -10px
|
||||
left: 10px
|
||||
z-index: 0
|
||||
|
||||
.opponent-team-icon
|
||||
height: 60px
|
||||
position: relative
|
||||
top: 10px
|
||||
right: 10px
|
||||
z-index: 0
|
||||
float: right
|
||||
-moz-transform: scaleX(-1)
|
||||
-o-transform: scaleX(-1)
|
||||
-webkit-transform: scaleX(-1)
|
||||
transform: scaleX(-1)
|
||||
filter: FlipH
|
||||
-ms-filter: "FlipH"
|
||||
|
||||
.opponent-icon
|
||||
position: relative
|
||||
float: right
|
||||
right: 0
|
||||
top: -10px
|
||||
-moz-transform: scaleX(-1)
|
||||
-o-transform: scaleX(-1)
|
||||
-webkit-transform: scaleX(-1)
|
||||
transform: scaleX(-1)
|
||||
filter: FlipH
|
||||
-ms-filter: "FlipH"
|
||||
z-index: 1
|
||||
|
||||
.name-label
|
||||
border-bottom: 20px solid lightslategray
|
||||
height: 0
|
||||
width: 40%
|
||||
position: absolute
|
||||
bottom: 0
|
||||
color: black
|
||||
font-weight: bold
|
||||
text-align: center
|
||||
z-index: 2
|
||||
|
||||
span
|
||||
position: relative
|
||||
top: 1px
|
||||
|
||||
.my-name
|
||||
border-right: 15px solid transparent
|
||||
left: 0
|
||||
span
|
||||
left: 3px
|
||||
|
||||
.opponent-name
|
||||
border-left: 15px solid transparent
|
||||
right: 0
|
||||
//text-align: right
|
||||
span
|
||||
right: 3px
|
||||
|
||||
.difficulty
|
||||
border-top: 25px solid darkgray
|
||||
border-left: 20px solid transparent
|
||||
border-right: 20px solid transparent
|
||||
height: 0
|
||||
width: 30%
|
||||
position: absolute
|
||||
left: 35%
|
||||
top: 0
|
||||
color: black
|
||||
text-align: center
|
||||
font-size: 18px
|
||||
font-weight: bold
|
||||
|
||||
span
|
||||
position: relative
|
||||
top: -25px
|
||||
|
||||
.easy-option .difficulty
|
||||
border-top: 25px solid limegreen
|
||||
|
||||
.medium-option .difficulty
|
||||
border-top: 25px solid darkorange
|
||||
|
||||
.hard-option .difficulty
|
||||
border-top: 25px solid black
|
||||
color: white
|
||||
|
||||
.vs
|
||||
position: absolute
|
||||
left: 40%
|
||||
right: 40%
|
||||
text-align: center
|
||||
top: 35px
|
||||
font-size: 40px
|
||||
font-weight: bolder
|
||||
color: black
|
|
@ -1,12 +0,0 @@
|
|||
#ladder-team-view
|
||||
#rank-button
|
||||
margin-top: 15px
|
||||
|
||||
#competitors-column .well
|
||||
font-size: 18px
|
||||
padding: 7px
|
||||
|
||||
#your-score
|
||||
margin-top: 20px
|
||||
text-align: center
|
||||
font-size: 20px
|
|
@ -1,25 +1,35 @@
|
|||
#level-victory-modal
|
||||
.victory-banner
|
||||
float: right
|
||||
width: 150px
|
||||
position: relative
|
||||
|
||||
.modal-footer
|
||||
clear: both
|
||||
padding-top: 15px
|
||||
|
||||
p.sign-up-poke
|
||||
text-align: left
|
||||
margin-bottom: 10px
|
||||
|
||||
.sign-up-button
|
||||
margin-left: 20px
|
||||
|
||||
.next-level-button
|
||||
margin-bottom: 10px
|
||||
margin-right: 30px
|
||||
float: right
|
||||
|
||||
.sign-up-button
|
||||
float: right
|
||||
margin-left: 10px
|
||||
|
||||
.next-level-button
|
||||
float: right
|
||||
margin-left: 10px
|
||||
|
||||
.rating
|
||||
float: center
|
||||
margin-right: 22%
|
||||
float: left
|
||||
position: relative
|
||||
top: 5px
|
||||
span
|
||||
margin-right: 5px
|
||||
i
|
||||
cursor: pointer
|
||||
padding: 2px
|
||||
|
||||
|
||||
.review
|
||||
margin-top: 5px
|
||||
width: 100%
|
||||
|
@ -31,23 +41,10 @@
|
|||
width: 100%
|
||||
height: 80px
|
||||
box-sizing: border-box
|
||||
|
||||
|
||||
.share-buttons
|
||||
padding-top: 15px
|
||||
clear: both
|
||||
|
||||
.modal-header
|
||||
text-align: left
|
||||
|
||||
.victory-banner
|
||||
width: 450px
|
||||
position: absolute
|
||||
left: -150px
|
||||
|
||||
.modal-dialog
|
||||
margin-left: 30%
|
||||
padding-left: 300px
|
||||
width: 700px
|
||||
|
||||
.modal-footer
|
||||
margin: 0px
|
||||
padding: 0px
|
||||
text-align: center
|
||||
|
|
|
@ -8,7 +8,7 @@ block content
|
|||
p(data-i18n="account_settings.not_logged_in") Log in or create an account to change your settings.
|
||||
|
||||
else
|
||||
button.btn#save-button.disabled.secret(data-i18n="account_settings.saveBackups") Changes Save Automatically
|
||||
button.btn#save-button.disabled.secret(data-i18n="account_settings.autosave") Changes Save Automatically
|
||||
|
||||
ul.nav.nav-pills#settings-tabs
|
||||
li
|
||||
|
|
|
@ -86,7 +86,7 @@ block content
|
|||
li Slovak - Anon
|
||||
li Persian - Reza Habibi (Rehb)
|
||||
li Czech - vanous
|
||||
li Russian - fess89, ser-storchak
|
||||
li Russian - fess89, ser-storchak, Mr A
|
||||
li Ukrainian - fess89
|
||||
li Italian - flauta
|
||||
li Norwegian - bardeh
|
||||
|
|
|
@ -10,8 +10,9 @@ block content
|
|||
if me.get('anonymous')
|
||||
a.btn.btn-primary.open-modal-button(data-toggle="coco-modal", data-target="modal/signup", role="button") Sign Up to Create a New #{modelLabel}
|
||||
else
|
||||
a.btn.btn-primary.open-modal-button(href='#new-model-modal', role="button", data-toggle="modal") Create a New #{modelLabel}
|
||||
input#search(placeholder="Search #{modelLabel}s Here")
|
||||
a.btn.btn-primary.open-modal-button(href='#new-model-modal', role="button", data-toggle="modal" data-i18n="#{currentNew}") Create a New Something
|
||||
|
||||
input#search(data-i18n="[placeholder]#{currentSearch}")
|
||||
hr
|
||||
div.results
|
||||
table
|
||||
|
|
|
@ -3,3 +3,8 @@ extends /templates/base
|
|||
block content
|
||||
|
||||
h1.text-center(data-i18n="not_found.page_not_found") Page Not Found
|
||||
|
||||
num = Math.floor(Math.random() * 3) + 1
|
||||
|
||||
img(src="/images/pages/not_found/404_#{num}.png" class="not-found-image")
|
||||
|
||||
|
|
|
@ -2,17 +2,11 @@ extends /templates/base
|
|||
block content
|
||||
|
||||
div#level-column
|
||||
h3= level.get('name')
|
||||
div#level-description
|
||||
!{description}
|
||||
|
||||
if !me.get('anonymous')
|
||||
//a(href="http://www.youtube.com/watch?v=IFvfZiJGDsw&list=HL1392928835&feature=mh_lolz").intro-button.btn.btn-primary.btn-lg Watch the Video
|
||||
|
||||
a(href="/play/level/brawlwood-tutorial").intro-button.btn.btn-primary.btn-lg Play the Tutorial
|
||||
if levelDescription
|
||||
div!= levelDescription
|
||||
else
|
||||
h1= level.get('name')
|
||||
|
||||
hr
|
||||
|
||||
if me.get('anonymous')
|
||||
div#must-log-in
|
||||
p
|
||||
|
@ -23,41 +17,37 @@ block content
|
|||
|
||||
else
|
||||
div#columns.row
|
||||
div.column.col-md-2
|
||||
for team in teams
|
||||
div.column.col-md-6
|
||||
a(href="/play/ladder/#{levelID}/team/#{team.id}", style="background-color: #{team.primaryColor}").play-button.btn.btn-danger.btn-block.btn-lg
|
||||
div.column.col-md-4
|
||||
a(style="background-color: #{team.primaryColor}", data-team=team.id).play-button.btn.btn-danger.btn-block.btn-lg
|
||||
span Play As
|
||||
span= team.name
|
||||
|
||||
table.table.table-bordered.table-condensed.table-hover
|
||||
//(style="background-color: #{team.bgColor}")
|
||||
tr
|
||||
th(colspan=3, style="color: #{team.primaryColor}")
|
||||
span= team.name
|
||||
span Leaderboard
|
||||
tr
|
||||
th Score
|
||||
th.name-col-cell Name
|
||||
th
|
||||
|
||||
for session in team.leaderboard.topPlayers.models
|
||||
- var myRow = session.get('creator') == me.id
|
||||
tr(class=myRow ? "success" : "")
|
||||
td.score-cell= session.get('totalScore').toFixed(2)
|
||||
td.name-col-cell= session.get('creatorName') || "Anonymous"
|
||||
td
|
||||
if(!myRow)
|
||||
a(href="/play/level/#{level.get('slug') || level.id}/?team=#{team.otherTeam}&opponent=#{session.id}") Battle as #{team.otherTeam}!
|
||||
else
|
||||
a(href="/play/ladder/#{levelID}/team/#{team.id}") View your #{team.id} matches.
|
||||
|
||||
unless me.attributes.anonymous
|
||||
hr
|
||||
button.btn.btn-warning.btn-lg.highlight#simulate-button(style="margin-bottom:10px;") Simulate Games!
|
||||
p(id="simulation-status-text", style="display:inline; margin-left:10px;")
|
||||
if simulationStatus
|
||||
| #{simulationStatus}
|
||||
else
|
||||
| By simulating games you can get your game ranked faster!
|
||||
if me.isAdmin()
|
||||
button.btn.btn-danger.btn-lg.highlight#simulate-all-button(style="margin-bottom:10px; float: right;") RESET AND SIMULATE GAMES
|
||||
div.column.col-md-2
|
||||
|
||||
hr
|
||||
|
||||
ul.nav.nav-pills
|
||||
li.active
|
||||
a(href="#ladder", data-toggle="tab") Ladder
|
||||
li
|
||||
a(href="#my-matches", data-toggle="tab") My Matches
|
||||
li
|
||||
a(href="#simulate", data-toggle="tab") Simulate
|
||||
|
||||
div.tab-content
|
||||
.tab-pane.active.well#ladder
|
||||
#ladder-tab-view
|
||||
.tab-pane.well#my-matches
|
||||
#my-matches-tab-view
|
||||
.tab-pane.well#simulate
|
||||
p(id="simulation-status-text")
|
||||
if simulationStatus
|
||||
| #{simulationStatus}
|
||||
else
|
||||
| By simulating games you can get your game ranked faster!
|
||||
p
|
||||
button.btn.btn-warning.btn-lg.highlight#simulate-button() Simulate Games!
|
||||
if me.isAdmin()
|
||||
p
|
||||
button.btn.btn-danger.btn-lg.highlight#simulate-all-button() RESET AND SIMULATE GAMES
|
21
app/templates/play/ladder/ladder_tab.jade
Normal file
|
@ -0,0 +1,21 @@
|
|||
div#columns.row
|
||||
for team in teams
|
||||
div.column.col-md-6
|
||||
table.table.table-bordered.table-condensed.table-hover
|
||||
tr
|
||||
th(colspan=3, style="color: #{team.primaryColor}")
|
||||
span= team.name
|
||||
span Leaderboard
|
||||
tr
|
||||
th Score
|
||||
th.name-col-cell Name
|
||||
th
|
||||
|
||||
for session in team.leaderboard.topPlayers.models
|
||||
- var myRow = session.get('creator') == me.id
|
||||
tr(class=myRow ? "success" : "")
|
||||
td.score-cell= Math.round(session.get('totalScore') * 100)
|
||||
td.name-col-cell= session.get('creatorName') || "Anonymous"
|
||||
td
|
||||
a(href="/play/level/#{level.get('slug') || level.id}/?team=#{team.otherTeam}&opponent=#{session.id}")
|
||||
span Battle as #{team.otherTeam}!
|
53
app/templates/play/ladder/my_matches_tab.jade
Normal file
|
@ -0,0 +1,53 @@
|
|||
//if matches.length
|
||||
// p#your-score
|
||||
// span Your Current Score:
|
||||
// span
|
||||
// strong= score
|
||||
|
||||
div#columns.row
|
||||
for team in teams
|
||||
div.matches-column.col-md-6
|
||||
table.table.table-bordered.table-condensed
|
||||
|
||||
tr
|
||||
th(colspan=4, style="color: #{team.primaryColor}")
|
||||
span Your
|
||||
span
|
||||
span= team.name
|
||||
span
|
||||
span Matches
|
||||
|
||||
if team.session
|
||||
button.btn.btn-sm.btn-warning.pull-right.rank-button(data-session-id=team.session.id)
|
||||
span.unavailable.hidden No New Code to Rank
|
||||
span.rank.hidden Rank My Game!
|
||||
span.ranking.hidden Submitting...
|
||||
span.ranked.hidden Submitted for Ranking
|
||||
span.failed.hidden Failed to Rank
|
||||
|
||||
tr
|
||||
th Result
|
||||
th Opponent
|
||||
th When
|
||||
th
|
||||
for match in team.matches
|
||||
tr
|
||||
td.state-cell
|
||||
if match.state === 'win'
|
||||
span.win Win
|
||||
if match.state === 'loss'
|
||||
span.loss Loss
|
||||
if match.state === 'tie'
|
||||
span.tie Tie
|
||||
td.name-cell= match.opponentName || "Anonymous"
|
||||
td.time-cell= match.when
|
||||
td.battle-cell
|
||||
- var text = match.state === 'win' ? 'Watch your victory' : 'Defeat the ' + team.otherTeam
|
||||
a(href="/play/level/#{levelID}?team=#{team.id}&opponent=#{match.sessionID}")= text
|
||||
|
||||
if !team.matches.length
|
||||
tr
|
||||
td(colspan=4).alert.alert-warning
|
||||
| No ranked matches for this team!
|
||||
| Play against some competitors and then come back here to get your game ranked.
|
||||
|
72
app/templates/play/ladder/play_modal.jade
Normal file
|
@ -0,0 +1,72 @@
|
|||
extends /templates/modal/modal_base
|
||||
|
||||
block modal-header-content
|
||||
h3 Choose an Opponent
|
||||
|
||||
block modal-body-content
|
||||
|
||||
p.tutorial-suggestion
|
||||
span Not sure what's going on?
|
||||
|
|
||||
a(href="/play/level/brawlwood-tutorial") Play the tutorial first.
|
||||
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}")
|
||||
div.play-option
|
||||
img(src=myPortrait).my-icon.only-one
|
||||
img(src="/images/pages/play/ladder/"+teamID+"_ladder_tutorial.png", style="border: 1px solid #{teamColor}; background: #{teamBackgroundColor}").my-team-icon.img-circle.only-one
|
||||
img(src=genericPortrait).opponent-icon
|
||||
img(src="/images/pages/play/ladder/"+otherTeamID+"_ladder_tutorial.png", style="border: 1px solid #{opponentTeamColor}; background: #{opponentTeamBackgroundColor}").opponent-team-icon.img-circle
|
||||
div.my-name.name-label.only-one
|
||||
span= myName
|
||||
div.opponent-name.name-label
|
||||
span Simple AI
|
||||
div.difficulty
|
||||
span Warmup
|
||||
div.vs VS
|
||||
|
||||
if challengers.easy
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.easy.sessionID}")
|
||||
div.play-option.easy-option
|
||||
img(src=myPortrait).my-icon.only-one
|
||||
img(src="/images/pages/play/ladder/"+teamID+"_ladder_easy.png", style="border: 1px solid #{teamColor}; background: #{teamBackgroundColor}").my-team-icon.img-circle.only-one
|
||||
img(src=challengers.easy.opponentImageSource||genericPortrait).opponent-icon
|
||||
img(src="/images/pages/play/ladder/"+otherTeamID+"_ladder_easy.png", style="border: 1px solid #{opponentTeamColor}; background: #{opponentTeamBackgroundColor}").opponent-team-icon.img-circle
|
||||
div.my-name.name-label.only-one
|
||||
span= myName
|
||||
div.opponent-name.name-label
|
||||
span= challengers.easy.opponentName
|
||||
div.difficulty
|
||||
span Easy
|
||||
div.vs VS
|
||||
|
||||
if challengers.medium
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.medium.sessionID}")
|
||||
div.play-option.medium-option
|
||||
img(src=myPortrait).my-icon.only-one
|
||||
img(src="/images/pages/play/ladder/"+teamID+"_ladder_medium.png", style="border: 1px solid #{teamColor}; background: #{teamBackgroundColor}").my-team-icon.img-circle.only-one
|
||||
img(src=challengers.medium.opponentImageSource||genericPortrait).opponent-icon
|
||||
img(src="/images/pages/play/ladder/"+otherTeamID+"_ladder_medium.png", style="border: 1px solid #{opponentTeamColor}; background: #{opponentTeamBackgroundColor}").opponent-team-icon.img-circle
|
||||
div.my-name.name-label.only-one
|
||||
span= myName
|
||||
div.opponent-name.name-label
|
||||
span= challengers.medium.opponentName
|
||||
div.difficulty
|
||||
span Medium
|
||||
div.vs VS
|
||||
|
||||
if challengers.hard
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.hard.sessionID}")
|
||||
div.play-option.hard-option
|
||||
img(src=myPortrait).my-icon.only-one
|
||||
img(src="/images/pages/play/ladder/"+teamID+"_ladder_hard.png", style="border: 1px solid #{teamColor}; background: #{teamBackgroundColor}").my-team-icon.img-circle.only-one
|
||||
img(src=challengers.hard.opponentImageSource||genericPortrait).opponent-icon
|
||||
img(src="/images/pages/play/ladder/"+otherTeamID+"_ladder_hard.png", style="border: 1px solid #{opponentTeamColor}; background: #{opponentTeamBackgroundColor}").opponent-team-icon.img-circle
|
||||
div.my-name.name-label.only-one
|
||||
span= myName
|
||||
div.opponent-name.name-label
|
||||
span= challengers.hard.opponentName
|
||||
div.difficulty
|
||||
span Hard
|
||||
div.vs VS
|
||||
|
||||
block modal-footer
|
|
@ -1,112 +0,0 @@
|
|||
extends /templates/base
|
||||
block content
|
||||
|
||||
ol.breadcrumb
|
||||
li
|
||||
a(href="/") Home
|
||||
li
|
||||
a(href="/play/ladder/#{levelID}")= level.get('name')
|
||||
li.active= teamName
|
||||
|
||||
p
|
||||
| In this level, you play against everyone who has ever written strategies for the opposing forces.
|
||||
| Choose from the suggested players on the right, playing as many and as long as you like,
|
||||
| and when you are ready to test your grand strategy against the whole ladder, return and click the rank button.
|
||||
|
||||
p
|
||||
| After your first submission, your code will also continuously run against other players as they rank themselves.
|
||||
|
||||
if matches.length
|
||||
p#your-score
|
||||
span Your Current Score:
|
||||
span
|
||||
strong= score
|
||||
|
||||
|
||||
div#columns.row
|
||||
div#matches-column.col-md-6
|
||||
h3.pull-left Ranked Games
|
||||
button.btn.btn-warning.pull-right#rank-button
|
||||
span.unavailable.hidden No New Code to Rank
|
||||
span.rank.hidden Rank My Game!
|
||||
span.ranking.hidden Submitting...
|
||||
span.ranked.hidden Submitted for Ranking
|
||||
span.failed.hidden Failed to Rank
|
||||
|
||||
hr.clearfix(style="clear: both")
|
||||
|
||||
if matches.length
|
||||
table.table.table-bordered.table-condensed
|
||||
tr
|
||||
th Result
|
||||
th Opponent
|
||||
th When
|
||||
for match in matches
|
||||
tr
|
||||
td.state-cell
|
||||
if match.state === 'win'
|
||||
span.win Win
|
||||
if match.state === 'loss'
|
||||
span.loss Loss
|
||||
if match.state === 'tie'
|
||||
span.tie Tie
|
||||
td.name-cell= match.opponentName || "Anonymous"
|
||||
td.time-cell= match.when
|
||||
td.battle-cell
|
||||
- var text = match.state === 'win' ? 'Watch your victory' : 'Defeat the ' + otherTeamID
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{match.sessionID}")= text
|
||||
|
||||
else
|
||||
div.alert.alert-warning
|
||||
| No ranked matches played yet!
|
||||
| Play some competitors on the right and then come back to get your game ranked.
|
||||
|
||||
// finish this once matches are available
|
||||
|
||||
div#competitors-column.col-md-6
|
||||
h3 Your Competitors
|
||||
|
||||
.well.text-muted
|
||||
div.row
|
||||
div.col-md-2
|
||||
span.warmup Warmup
|
||||
span :
|
||||
div.col-md-10
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}")
|
||||
span.warmup Play #{teamID} vs Default #{otherTeamID}
|
||||
|
||||
if challengers.easy
|
||||
.well
|
||||
div.row.text-info.bg-info
|
||||
div.col-md-2
|
||||
span.easy Easy
|
||||
span :
|
||||
div.col-md-10
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.easy.sessionID}")
|
||||
span Play #{teamID} vs
|
||||
strong= challengers.easy.opponentName
|
||||
span #{otherTeamID}
|
||||
|
||||
if challengers.medium
|
||||
.well
|
||||
div.row.text-warning.bg-warning
|
||||
div.col-md-2
|
||||
span.medium Medium
|
||||
span :
|
||||
div.col-md-10
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.medium.sessionID}")
|
||||
span Play #{teamID} vs
|
||||
strong= challengers.medium.opponentName
|
||||
span #{otherTeamID}
|
||||
|
||||
if challengers.hard
|
||||
.well
|
||||
div.row.text-danger.bg-danger
|
||||
div.col-md-2
|
||||
span.hard Hard
|
||||
span :
|
||||
div.col-md-10
|
||||
a(href="/play/level/#{levelID}?team=#{teamID}&opponent=#{challengers.hard.sessionID}")
|
||||
span Play #{teamID} vs
|
||||
strong= challengers.hard.opponentName
|
||||
span #{otherTeamID}
|
|
@ -7,5 +7,6 @@
|
|||
if hasBar
|
||||
span.prop-value.bar-prop
|
||||
.bar
|
||||
span.prop-value.bar-prop-value
|
||||
else
|
||||
span.prop-value
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
.modal-dialog
|
||||
|
||||
img.victory-banner(src="/images/level/victory.png", alt="")
|
||||
|
||||
.modal-header
|
||||
button(type='button', data-dismiss="modal", aria-hidden="true").close ×
|
||||
h3
|
||||
|
@ -11,7 +9,9 @@
|
|||
span= levelName
|
||||
span(data-i18n="play_level.victory_title_suffix") Complete
|
||||
|
||||
.modal-body!= body
|
||||
.modal-body
|
||||
img.victory-banner(src="/images/level/victory.png", alt="")
|
||||
div!= body
|
||||
|
||||
.modal-footer
|
||||
if hasNextLevel
|
||||
|
|
|
@ -39,6 +39,7 @@ class LiveEditingMarkup extends TreemaNode.nodeMap.ace
|
|||
filename: InkBlob.filename
|
||||
mimetype: InkBlob.mimetype
|
||||
path: @settings.filePath
|
||||
force: true
|
||||
|
||||
@uploadingPath = [@settings.filePath, InkBlob.filename].join('/')
|
||||
$.ajax('/file', { type: 'POST', data: body, success: @onFileUploaded })
|
||||
|
@ -147,9 +148,8 @@ class SoundFileTreema extends TreemaNode.nodeMap.string
|
|||
filename: InkBlob.filename
|
||||
mimetype: InkBlob.mimetype
|
||||
path: @settings.filePath
|
||||
force: true
|
||||
|
||||
# Automatically overwrite if the same path was put in here before
|
||||
body.force = true # if InkBlob.filename is @data
|
||||
@uploadingPath = [@settings.filePath, InkBlob.filename].join('/')
|
||||
$.ajax('/file', { type: 'POST', data: body, success: @onFileUploaded })
|
||||
|
||||
|
@ -185,9 +185,8 @@ class ImageFileTreema extends TreemaNode.nodeMap.string
|
|||
filename: InkBlob.filename
|
||||
mimetype: InkBlob.mimetype
|
||||
path: @settings.filePath
|
||||
force: true
|
||||
|
||||
# Automatically overwrite if the same path was put in here before
|
||||
body.force = true # if InkBlob.filename is @data
|
||||
@uploadingPath = [@settings.filePath, InkBlob.filename].join('/')
|
||||
$.ajax('/file', { type: 'POST', data: body, success: @onFileUploaded })
|
||||
|
||||
|
|
|
@ -11,6 +11,10 @@ module.exports = class HomeView extends View
|
|||
events:
|
||||
'mouseover #beginner-campaign': 'onMouseOverButton'
|
||||
'mouseout #beginner-campaign': 'onMouseOutButton'
|
||||
|
||||
constructor: ->
|
||||
super(arguments...)
|
||||
ThangType.loadUniversalWizard()
|
||||
|
||||
getRenderData: ->
|
||||
c = super()
|
||||
|
@ -28,12 +32,8 @@ module.exports = class HomeView extends View
|
|||
@$el.find('.modal').on 'shown.bs.modal', ->
|
||||
$('input:visible:first', @).focus()
|
||||
|
||||
wizOriginal = "52a00d55cf1818f2be00000b"
|
||||
url = "/db/thang_type/#{wizOriginal}/version"
|
||||
@wizardType = new ThangType()
|
||||
@wizardType.url = -> url
|
||||
@wizardType.fetch()
|
||||
@wizardType.once 'sync', @initCanvas
|
||||
@wizardType = ThangType.wizardType
|
||||
if @wizardType.loaded then @initCanvas else @wizardType.once 'sync', @initCanvas, @
|
||||
|
||||
# Try to find latest level and set "Play" link to go to that level
|
||||
if localStorage?
|
||||
|
@ -48,7 +48,7 @@ module.exports = class HomeView extends View
|
|||
else
|
||||
console.log("TODO: Insert here code to get latest level played from the database. If this can't be found, we just let the user play the first level.")
|
||||
|
||||
initCanvas: =>
|
||||
initCanvas: ->
|
||||
@stage = new createjs.Stage($('#beginner-campaign canvas', @$el)[0])
|
||||
@createWizard()
|
||||
|
||||
|
|
|
@ -38,8 +38,15 @@ module.exports = class RootView extends CocoView
|
|||
location.hash = ''
|
||||
location.hash = hash
|
||||
@buildLanguages()
|
||||
|
||||
afterRender: ->
|
||||
super(arguments...)
|
||||
@chooseTab(location.hash.replace('#','')) if location.hash
|
||||
|
||||
# TODO: automate tabs to put in hashes and navigate to them here
|
||||
chooseTab: (category) ->
|
||||
$("a[href='##{category}']", @$el).tab('show')
|
||||
|
||||
# TODO: automate tabs to put in hashes when they are clicked
|
||||
|
||||
buildLanguages: ->
|
||||
$select = @$el.find(".language-dropdown").empty()
|
||||
|
|
|
@ -28,14 +28,20 @@ module.exports = class ThangTypeHomeView extends View
|
|||
|
||||
getRenderData: ->
|
||||
context = super()
|
||||
context.modelLabel = @modelLabel
|
||||
switch @modelLabel
|
||||
when 'Level'
|
||||
context.currentEditor = 'editor.level_title'
|
||||
context.currentNew = 'editor.new_level_title'
|
||||
context.currentSearch = 'editor.level_search_title'
|
||||
when 'Thang Type'
|
||||
context.currentEditor = 'editor.thang_title'
|
||||
context.currentNew = 'editor.new_thang_title'
|
||||
context.currentSearch = 'editor.thang_search_title'
|
||||
when 'Article'
|
||||
context.currentEditor = 'editor.article_title'
|
||||
context.currentNew = 'editor.new_article_title'
|
||||
context.currentSearch = 'editor.article_search_title'
|
||||
@$el.i18n()
|
||||
context
|
||||
|
||||
constructor: (options) ->
|
||||
|
@ -77,6 +83,7 @@ module.exports = class ThangTypeHomeView extends View
|
|||
documents = @collection.models
|
||||
table = $(@tableTemplate(documents:documents))
|
||||
@$el.find('table').replaceWith(table)
|
||||
@$el.find('table').i18n()
|
||||
|
||||
removeOldSearch: ->
|
||||
return unless @collection?
|
||||
|
|
100
app/views/play/ladder/ladder_tab.coffee
Normal file
|
@ -0,0 +1,100 @@
|
|||
CocoView = require 'views/kinds/CocoView'
|
||||
Level = require 'models/Level'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
CocoCollection = require 'models/CocoCollection'
|
||||
LeaderboardCollection = require 'collections/LeaderboardCollection'
|
||||
{teamDataFromLevel} = require './utils'
|
||||
|
||||
HIGHEST_SCORE = 1000000
|
||||
|
||||
class LevelSessionsCollection extends CocoCollection
|
||||
url: ''
|
||||
model: LevelSession
|
||||
|
||||
constructor: (levelID) ->
|
||||
super()
|
||||
@url = "/db/level/#{levelID}/all_sessions"
|
||||
|
||||
module.exports = class LadderTabView extends CocoView
|
||||
id: 'ladder-tab-view'
|
||||
template: require 'templates/play/ladder/ladder_tab'
|
||||
startsLoading: true
|
||||
|
||||
constructor: (options, @level, @sessions) ->
|
||||
super(options)
|
||||
@teams = teamDataFromLevel @level
|
||||
@leaderboards = {}
|
||||
@refreshLadder()
|
||||
|
||||
refreshLadder: ->
|
||||
for team in @teams
|
||||
@leaderboards[team.id]?.off 'sync'
|
||||
# teamSession = _.find @sessions.models, (session) -> session.get('team') is team.id
|
||||
teamSession = null
|
||||
# console.log "Team session: #{JSON.stringify teamSession}"
|
||||
@leaderboards[team.id] = new LeaderboardData(@level, team.id, teamSession)
|
||||
@leaderboards[team.id].once 'sync', @onLeaderboardLoaded, @
|
||||
|
||||
onLeaderboardLoaded: -> @renderMaybe()
|
||||
|
||||
renderMaybe: ->
|
||||
leaderboardModels = _.values(@leaderboards)
|
||||
return unless _.every leaderboardModels, (loader) -> loader.loaded
|
||||
@startsLoading = false
|
||||
@render()
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
ctx.level = @level
|
||||
ctx.link = "/play/level/#{@level.get('name')}"
|
||||
ctx.teams = @teams
|
||||
team.leaderboard = @leaderboards[team.id] for team in @teams
|
||||
ctx.levelID = @levelID
|
||||
ctx
|
||||
|
||||
class LeaderboardData
|
||||
constructor: (@level, @team, @session) ->
|
||||
_.extend @, Backbone.Events
|
||||
@topPlayers = new LeaderboardCollection(@level, {order:-1, scoreOffset: HIGHEST_SCORE, team: @team, limit: if @session then 10 else 20})
|
||||
@topPlayers.fetch()
|
||||
@topPlayers.comparator = (model) ->
|
||||
return -model.get('totalScore')
|
||||
@topPlayers.sort()
|
||||
|
||||
@topPlayers.once 'sync', @leaderboardPartLoaded, @
|
||||
|
||||
# if @session
|
||||
# score = @session.get('totalScore') or 25
|
||||
# @playersAbove = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 4, team: @team})
|
||||
# @playersAbove.fetch()
|
||||
# @playersAbove.once 'sync', @leaderboardPartLoaded, @
|
||||
# @playersBelow = new LeaderboardCollection(@level, {order:-1, scoreOffset: score, limit: 4, team: @team})
|
||||
# @playersBelow.fetch()
|
||||
# @playersBelow.once 'sync', @leaderboardPartLoaded, @
|
||||
|
||||
leaderboardPartLoaded: ->
|
||||
if @session
|
||||
if @topPlayers.loaded # and @playersAbove.loaded and @playersBelow.loaded
|
||||
@loaded = true
|
||||
@fetchNames()
|
||||
else
|
||||
@loaded = true
|
||||
@fetchNames()
|
||||
|
||||
fetchNames: ->
|
||||
sessionCollections = [@topPlayers, @playersAbove, @playersBelow]
|
||||
sessionCollections = (s for s in sessionCollections when s)
|
||||
ids = []
|
||||
for collection in sessionCollections
|
||||
ids.push model.get('creator') for model in collection.models
|
||||
|
||||
success = (nameMap) =>
|
||||
for collection in sessionCollections
|
||||
session.set('creatorName', nameMap[session.get('creator')]) for session in collection.models
|
||||
@trigger 'sync'
|
||||
|
||||
$.ajax('/db/user/-/names', {
|
||||
data: {ids: ids}
|
||||
type: 'POST'
|
||||
success: success
|
||||
})
|
108
app/views/play/ladder/my_matches_tab.coffee
Normal file
|
@ -0,0 +1,108 @@
|
|||
CocoView = require 'views/kinds/CocoView'
|
||||
Level = require 'models/Level'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
LeaderboardCollection = require 'collections/LeaderboardCollection'
|
||||
{teamDataFromLevel} = require './utils'
|
||||
|
||||
module.exports = class MyMatchesTabView extends CocoView
|
||||
id: 'my-matches-tab-view'
|
||||
template: require 'templates/play/ladder/my_matches_tab'
|
||||
startsLoading: true
|
||||
|
||||
events:
|
||||
'click .rank-button': 'rankSession'
|
||||
|
||||
constructor: (options, @level, @sessions) ->
|
||||
super(options)
|
||||
@refreshMatches()
|
||||
|
||||
refreshMatches: ->
|
||||
@teams = teamDataFromLevel @level
|
||||
@nameMap = {}
|
||||
@loadNames()
|
||||
|
||||
loadNames: ->
|
||||
ids = []
|
||||
for session in @sessions.models
|
||||
ids.push match.opponents[0].userID for match in session.get('matches') or []
|
||||
|
||||
success = (@nameMap) =>
|
||||
for session in @sessions.models
|
||||
for match in session.get('matches') or []
|
||||
opponent = match.opponents[0]
|
||||
opponent.userName = @nameMap[opponent.userID]
|
||||
@finishRendering()
|
||||
|
||||
$.ajax('/db/user/-/names', {
|
||||
data: {ids: ids}
|
||||
type: 'POST'
|
||||
success: success
|
||||
})
|
||||
|
||||
finishRendering: ->
|
||||
@startsLoading = false
|
||||
@render()
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
ctx.level = @level
|
||||
ctx.levelID = @level.get('slug') or @level.id
|
||||
ctx.teams = @teams
|
||||
|
||||
convertMatch = (match) =>
|
||||
opponent = match.opponents[0]
|
||||
state = 'win'
|
||||
state = 'loss' if match.metrics.rank > opponent.metrics.rank
|
||||
state = 'tie' if match.metrics.rank is opponent.metrics.rank
|
||||
{
|
||||
state: state
|
||||
opponentName: @nameMap[opponent.userID]
|
||||
opponentID: opponent.userID
|
||||
when: moment(match.date).fromNow()
|
||||
sessionID: opponent.sessionID
|
||||
}
|
||||
|
||||
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.matches = (convertMatch(match) for match in team.session?.get('matches') or [])
|
||||
team.matches.reverse()
|
||||
team.score = (team.session?.get('totalScore') or 10).toFixed(2)
|
||||
|
||||
ctx
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@$el.find('.rank-button').each (i, el) =>
|
||||
button = $(el)
|
||||
sessionID = button.data('session-id')
|
||||
session = _.find @sessions.models, { id: sessionID }
|
||||
@setRankingButtonText button, if @readyToRank(session) then 'rank' else 'unavailable'
|
||||
|
||||
readyToRank: (session) ->
|
||||
c1 = session?.get('code')
|
||||
c2 = session?.get('submittedCode')
|
||||
c1 and not _.isEqual(c1, c2)
|
||||
|
||||
rankSession: (e) ->
|
||||
console.log "Clicked"
|
||||
button = $(e.target).closest('.rank-button')
|
||||
sessionID = button.data('session-id')
|
||||
session = _.find @sessions.models, { id: sessionID }
|
||||
return unless @readyToRank(session)
|
||||
|
||||
@setRankingButtonText(button, 'ranking')
|
||||
success = => @setRankingButtonText(button, 'ranked')
|
||||
failure = => @setRankingButtonText(button, 'failed')
|
||||
|
||||
$.ajax '/queue/scoring', {
|
||||
type: 'POST'
|
||||
data: { session: sessionID }
|
||||
success: success
|
||||
failure: failure
|
||||
}
|
||||
|
||||
setRankingButtonText: (rankButton, spanClass) ->
|
||||
rankButton.find('span').addClass('hidden')
|
||||
rankButton.find(".#{spanClass}").removeClass('hidden')
|
||||
rankButton.toggleClass 'disabled', spanClass isnt 'rank'
|
162
app/views/play/ladder/play_modal.coffee
Normal file
|
@ -0,0 +1,162 @@
|
|||
View = require 'views/kinds/ModalView'
|
||||
template = require 'templates/play/ladder/play_modal'
|
||||
ThangType = require 'models/ThangType'
|
||||
{me} = require 'lib/auth'
|
||||
LeaderboardCollection = require 'collections/LeaderboardCollection'
|
||||
{teamDataFromLevel} = require './utils'
|
||||
|
||||
module.exports = class LadderPlayModal extends View
|
||||
id: "ladder-play-modal"
|
||||
template: template
|
||||
closeButton: true
|
||||
startsLoading: true
|
||||
|
||||
constructor: (options, @level, @session, @team) ->
|
||||
super(options)
|
||||
@nameMap = {}
|
||||
@otherTeam = if team is 'ogres' then 'humans' else 'ogres'
|
||||
@startLoadingChallengersMaybe()
|
||||
@wizardType = ThangType.loadUniversalWizard()
|
||||
|
||||
# PART 1: Load challengers from the db unless some are in the matches
|
||||
|
||||
startLoadingChallengersMaybe: ->
|
||||
matches = @session?.get('matches')
|
||||
if matches?.length then @loadNames() else @loadChallengers()
|
||||
|
||||
loadChallengers: ->
|
||||
@challengersCollection = new ChallengersData(@level, @team, @otherTeam, @session)
|
||||
@challengersCollection.on 'sync', @loadNames, @
|
||||
|
||||
# PART 2: Loading the names of the other users
|
||||
|
||||
loadNames: ->
|
||||
@challengers = @getChallengers()
|
||||
ids = (challenger.opponentID for challenger in _.values @challengers)
|
||||
|
||||
success = (@nameMap) =>
|
||||
for challenger in _.values(@challengers)
|
||||
challenger.opponentName = @nameMap[challenger.opponentID]?.name or 'Anoner'
|
||||
challenger.opponentWizard = @nameMap[challenger.opponentID]?.wizard or {}
|
||||
@checkWizardLoaded()
|
||||
|
||||
$.ajax('/db/user/-/names', {
|
||||
data: {ids: ids, wizard: true}
|
||||
type: 'POST'
|
||||
success: success
|
||||
})
|
||||
|
||||
# PART 3: Make sure wizard is loaded
|
||||
|
||||
checkWizardLoaded: ->
|
||||
if @wizardType.loaded then @finishRendering() else @wizardType.once 'sync', @finishRendering, @
|
||||
|
||||
# PART 4: Render
|
||||
|
||||
finishRendering: ->
|
||||
@startsLoading = false
|
||||
@render()
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
ctx.level = @level
|
||||
ctx.levelID = @level.get('slug') or @level.id
|
||||
ctx.teamName = _.string.titleize @team
|
||||
ctx.teamID = @team
|
||||
ctx.otherTeamID = @otherTeam
|
||||
|
||||
teamsList = teamDataFromLevel @level
|
||||
teams = {}
|
||||
teams[team.id] = team for team in teamsList
|
||||
ctx.teamColor = teams[@team].primaryColor
|
||||
ctx.teamBackgroundColor = teams[@team].bgColor
|
||||
ctx.opponentTeamColor = teams[@otherTeam].primaryColor
|
||||
ctx.opponentTeamBackgroundColor = teams[@otherTeam].bgColor
|
||||
|
||||
ctx.challengers = @challengers or {}
|
||||
for challenger in _.values ctx.challengers
|
||||
continue unless challenger and @wizardType.loaded
|
||||
if (not challenger.opponentImageSource) and challenger.opponentWizard?.colorConfig
|
||||
challenger.opponentImageSource = @wizardType.getPortraitSource(
|
||||
{colorConfig: challenger.opponentWizard.colorConfig})
|
||||
|
||||
if @wizardType.loaded
|
||||
ctx.genericPortrait = @wizardType.getPortraitSource()
|
||||
myColorConfig = me.get('wizard')?.colorConfig
|
||||
ctx.myPortrait = if myColorConfig then @wizardType.getPortraitSource({colorConfig: myColorConfig}) else ctx.genericPortrait
|
||||
|
||||
ctx.myName = me.get('name') || 'Newcomer'
|
||||
ctx
|
||||
|
||||
# Choosing challengers
|
||||
|
||||
getChallengers: ->
|
||||
# make an object of challengers to everything needed to link to them
|
||||
challengers = {}
|
||||
if @challengersCollection
|
||||
easyInfo = @challengeInfoFromSession(@challengersCollection.easyPlayer.models[0])
|
||||
mediumInfo = @challengeInfoFromSession(@challengersCollection.mediumPlayer.models[0])
|
||||
hardInfo = @challengeInfoFromSession(@challengersCollection.hardPlayer.models[0])
|
||||
else
|
||||
matches = @session.get('matches')
|
||||
won = (m for m in matches when m.metrics.rank < m.opponents[0].metrics.rank)
|
||||
lost = (m for m in matches when m.metrics.rank > m.opponents[0].metrics.rank)
|
||||
tied = (m for m in matches when m.metrics.rank is m.opponents[0].metrics.rank)
|
||||
easyInfo = @challengeInfoFromMatches(won)
|
||||
mediumInfo = @challengeInfoFromMatches(tied)
|
||||
hardInfo = @challengeInfoFromMatches(lost)
|
||||
@addChallenger easyInfo, challengers, 'easy'
|
||||
@addChallenger mediumInfo, challengers, 'medium'
|
||||
@addChallenger hardInfo, challengers, 'hard'
|
||||
challengers
|
||||
|
||||
addChallenger: (info, challengers, title) ->
|
||||
# check for duplicates first
|
||||
return unless info
|
||||
for key, value of challengers
|
||||
return if value.sessionID is info.sessionID
|
||||
challengers[title] = info
|
||||
|
||||
challengeInfoFromSession: (session) ->
|
||||
# given a model from the db, return info needed for a link to the match
|
||||
return unless session
|
||||
return {
|
||||
sessionID: session.id
|
||||
opponentID: session.get('creator')
|
||||
}
|
||||
|
||||
challengeInfoFromMatches: (matches) ->
|
||||
return unless matches?.length
|
||||
match = _.sample matches
|
||||
opponent = match.opponents[0]
|
||||
return {
|
||||
sessionID: opponent.sessionID
|
||||
opponentID: opponent.userID
|
||||
}
|
||||
|
||||
|
||||
class ChallengersData
|
||||
constructor: (@level, @team, @otherTeam, @session) ->
|
||||
_.extend @, Backbone.Events
|
||||
score = @session?.get('totalScore') or 25
|
||||
@easyPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score - 5, limit: 1, team: @otherTeam})
|
||||
@easyPlayer.fetch()
|
||||
@easyPlayer.once 'sync', @challengerLoaded, @
|
||||
@mediumPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 1, team: @otherTeam})
|
||||
@mediumPlayer.fetch()
|
||||
@mediumPlayer.once 'sync', @challengerLoaded, @
|
||||
@hardPlayer = new LeaderboardCollection(@level, {order:-1, scoreOffset: score + 5, limit: 1, team: @otherTeam})
|
||||
@hardPlayer.fetch()
|
||||
@hardPlayer.once 'sync', @challengerLoaded, @
|
||||
|
||||
challengerLoaded: ->
|
||||
if @allLoaded()
|
||||
@loaded = true
|
||||
@trigger 'sync'
|
||||
|
||||
playerIDs: ->
|
||||
collections = [@easyPlayer, @mediumPlayer, @hardPlayer]
|
||||
(c.models[0].get('creator') for c in collections when c?.models[0])
|
||||
|
||||
allLoaded: ->
|
||||
_.all [@easyPlayer.loaded, @mediumPlayer.loaded, @hardPlayer.loaded]
|
|
@ -1,195 +0,0 @@
|
|||
RootView = require 'views/kinds/RootView'
|
||||
Level = require 'models/Level'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
LeaderboardCollection = require 'collections/LeaderboardCollection'
|
||||
|
||||
module.exports = class LadderTeamView extends RootView
|
||||
id: 'ladder-team-view'
|
||||
template: require 'templates/play/ladder/team'
|
||||
startsLoading: true
|
||||
|
||||
events:
|
||||
'click #rank-button': 'rankSession'
|
||||
|
||||
# PART 1: Loading Level/Session
|
||||
|
||||
constructor: (options, @levelID, @team) ->
|
||||
super(options)
|
||||
@otherTeam = if team is 'ogres' then 'humans' else 'ogres'
|
||||
@level = new Level(_id:@levelID)
|
||||
@level.fetch()
|
||||
@level.once 'sync', @onLevelLoaded, @
|
||||
|
||||
url = "/db/level/#{@levelID}/session?team=#{@team}"
|
||||
@session = new LevelSession()
|
||||
@session.url = -> url
|
||||
@session.fetch()
|
||||
@session.once 'sync', @onSessionLoaded, @
|
||||
|
||||
onLevelLoaded: -> @startLoadingChallengersMaybe()
|
||||
onSessionLoaded: -> @startLoadingChallengersMaybe()
|
||||
|
||||
# PART 2: Loading some challengers if we don't have any matches yet
|
||||
|
||||
startLoadingChallengersMaybe: ->
|
||||
return unless @level.loaded and @session.loaded
|
||||
matches = @session.get('matches')
|
||||
if matches?.length then @loadNames() else @loadChallengers()
|
||||
|
||||
loadChallengers: ->
|
||||
@challengers = new ChallengersData(@level, @team, @otherTeam, @session)
|
||||
@challengers.on 'sync', @loadNames, @
|
||||
|
||||
# PART 3: Loading the names of the other users
|
||||
|
||||
loadNames: ->
|
||||
ids = []
|
||||
ids.push match.opponents[0].userID for match in @session.get('matches') or []
|
||||
ids = ids.concat(@challengers.playerIDs()) if @challengers
|
||||
|
||||
success = (@nameMap) =>
|
||||
for match in @session.get('matches') or []
|
||||
opponent = match.opponents[0]
|
||||
opponent.userName = @nameMap[opponent.userID]
|
||||
@finishRendering()
|
||||
|
||||
$.ajax('/db/user/-/names', {
|
||||
data: {ids: ids}
|
||||
type: 'POST'
|
||||
success: success
|
||||
})
|
||||
|
||||
# PART 4: Rendering
|
||||
|
||||
finishRendering: ->
|
||||
@startsLoading = false
|
||||
@render()
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
ctx.level = @level
|
||||
ctx.levelID = @levelID
|
||||
ctx.teamName = _.string.titleize @team
|
||||
ctx.teamID = @team
|
||||
ctx.otherTeamID = @otherTeam
|
||||
ctx.challengers = if not @startsLoading then @getChallengers() else {}
|
||||
ctx.readyToRank = @readyToRank()
|
||||
|
||||
convertMatch = (match) =>
|
||||
opponent = match.opponents[0]
|
||||
state = 'win'
|
||||
state = 'loss' if match.metrics.rank > opponent.metrics.rank
|
||||
state = 'tie' if match.metrics.rank is opponent.metrics.rank
|
||||
{
|
||||
state: state
|
||||
opponentName: @nameMap[opponent.userID]
|
||||
opponentID: opponent.userID
|
||||
when: moment(match.date).fromNow()
|
||||
sessionID: opponent.sessionID
|
||||
}
|
||||
|
||||
ctx.matches = (convertMatch(match) for match in @session.get('matches') or [])
|
||||
ctx.matches.reverse()
|
||||
ctx.score = (@session.get('totalScore') or 10).toFixed(2)
|
||||
ctx
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
@setRankingButtonText(if @readyToRank() then 'rank' else 'unavailable')
|
||||
|
||||
readyToRank: ->
|
||||
c1 = @session.get('code')
|
||||
c2 = @session.get('submittedCode')
|
||||
c1 and not _.isEqual(c1, c2)
|
||||
|
||||
getChallengers: ->
|
||||
# make an object of challengers to everything needed to link to them
|
||||
challengers = {}
|
||||
if @challengers
|
||||
easyInfo = @challengeInfoFromSession(@challengers.easyPlayer.models[0])
|
||||
mediumInfo = @challengeInfoFromSession(@challengers.mediumPlayer.models[0])
|
||||
hardInfo = @challengeInfoFromSession(@challengers.hardPlayer.models[0])
|
||||
else
|
||||
matches = @session.get('matches')
|
||||
won = (m for m in matches when m.metrics.rank < m.opponents[0].metrics.rank)
|
||||
lost = (m for m in matches when m.metrics.rank > m.opponents[0].metrics.rank)
|
||||
tied = (m for m in matches when m.metrics.rank is m.opponents[0].metrics.rank)
|
||||
easyInfo = @challengeInfoFromMatches(won)
|
||||
mediumInfo = @challengeInfoFromMatches(tied)
|
||||
hardInfo = @challengeInfoFromMatches(lost)
|
||||
@addChallenger easyInfo, challengers, 'easy'
|
||||
@addChallenger mediumInfo, challengers, 'medium'
|
||||
@addChallenger hardInfo, challengers, 'hard'
|
||||
challengers
|
||||
|
||||
addChallenger: (info, challengers, title) ->
|
||||
# check for duplicates first
|
||||
return unless info
|
||||
for key, value of challengers
|
||||
return if value.sessionID is info.sessionID
|
||||
challengers[title] = info
|
||||
|
||||
challengeInfoFromSession: (session) ->
|
||||
# given a model from the db, return info needed for a link to the match
|
||||
return unless session
|
||||
return {
|
||||
sessionID: session.id
|
||||
opponentName: @nameMap[session.get('creator')] or 'Anoner'
|
||||
opponentID: session.get('creator')
|
||||
}
|
||||
|
||||
challengeInfoFromMatches: (matches) ->
|
||||
return unless matches?.length
|
||||
match = _.sample matches
|
||||
opponent = match.opponents[0]
|
||||
return {
|
||||
sessionID: opponent.sessionID
|
||||
opponentName: opponent.userName or 'Anoner'
|
||||
opponentID: opponent.userID
|
||||
}
|
||||
|
||||
rankSession: ->
|
||||
return unless @readyToRank()
|
||||
@setRankingButtonText('ranking')
|
||||
|
||||
success = => @setRankingButtonText('ranked')
|
||||
failure = => @setRankingButtonText('failed')
|
||||
|
||||
$.ajax '/queue/scoring', {
|
||||
type: 'POST'
|
||||
data: { session: @session.id }
|
||||
success: success
|
||||
failure: failure
|
||||
}
|
||||
|
||||
setRankingButtonText: (spanClass) ->
|
||||
rankButton = $('#rank-button')
|
||||
rankButton.find('span').addClass('hidden')
|
||||
rankButton.find(".#{spanClass}").removeClass('hidden')
|
||||
rankButton.toggleClass 'disabled', spanClass isnt 'rank'
|
||||
|
||||
class ChallengersData
|
||||
constructor: (@level, @team, @otherTeam, @session) ->
|
||||
_.extend @, Backbone.Events
|
||||
score = @session?.get('totalScore') or 25
|
||||
@easyPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score - 5, limit: 1, team: @otherTeam})
|
||||
@easyPlayer.fetch()
|
||||
@easyPlayer.once 'sync', @challengerLoaded, @
|
||||
@mediumPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 1, team: @otherTeam})
|
||||
@mediumPlayer.fetch()
|
||||
@mediumPlayer.once 'sync', @challengerLoaded, @
|
||||
@hardPlayer = new LeaderboardCollection(@level, {order:-1, scoreOffset: score + 5, limit: 1, team: @otherTeam})
|
||||
@hardPlayer.fetch()
|
||||
@hardPlayer.once 'sync', @challengerLoaded, @
|
||||
|
||||
challengerLoaded: ->
|
||||
if @allLoaded()
|
||||
@loaded = true
|
||||
@trigger 'sync'
|
||||
|
||||
playerIDs: ->
|
||||
collections = [@easyPlayer, @mediumPlayer, @hardPlayer]
|
||||
(c.models[0].get('creator') for c in collections when c?.models[0])
|
||||
|
||||
allLoaded: ->
|
||||
_.all [@easyPlayer.loaded, @mediumPlayer.loaded, @hardPlayer.loaded]
|
22
app/views/play/ladder/utils.coffee
Normal file
|
@ -0,0 +1,22 @@
|
|||
{hslToHex} = require 'lib/utils'
|
||||
|
||||
module.exports.teamDataFromLevel = (level) ->
|
||||
alliedSystem = _.find level.get('systems'), (value) -> value.config?.teams?
|
||||
teamNames = (teamName for teamName, teamConfig of alliedSystem.config.teams when teamConfig.playable)
|
||||
teamConfigs = alliedSystem.config.teams
|
||||
|
||||
teams = []
|
||||
for team in teamNames or []
|
||||
otherTeam = if team is 'ogres' then 'humans' else 'ogres'
|
||||
color = teamConfigs[team].color
|
||||
bgColor = hslToHex([color.hue, color.saturation, color.lightness + (1 - color.lightness) * 0.5])
|
||||
primaryColor = hslToHex([color.hue, 0.5, 0.5])
|
||||
teams.push({
|
||||
id: team
|
||||
name: _.string.titleize(team)
|
||||
otherTeam: otherTeam
|
||||
bgColor: bgColor
|
||||
primaryColor: primaryColor
|
||||
})
|
||||
|
||||
teams
|
|
@ -3,8 +3,11 @@ Level = require 'models/Level'
|
|||
Simulator = require 'lib/simulator/Simulator'
|
||||
LevelSession = require 'models/LevelSession'
|
||||
CocoCollection = require 'models/CocoCollection'
|
||||
LeaderboardCollection = require 'collections/LeaderboardCollection'
|
||||
{hslToHex} = require 'lib/utils'
|
||||
{teamDataFromLevel} = require './ladder/utils'
|
||||
|
||||
LadderTabView = require './ladder/ladder_tab'
|
||||
MyMatchesTabView = require './ladder/my_matches_tab'
|
||||
LadderPlayModal = require './ladder/play_modal'
|
||||
|
||||
HIGHEST_SCORE = 1000000
|
||||
|
||||
|
@ -14,7 +17,7 @@ class LevelSessionsCollection extends CocoCollection
|
|||
|
||||
constructor: (levelID) ->
|
||||
super()
|
||||
@url = "/db/level/#{levelID}/all_sessions"
|
||||
@url = "/db/level/#{levelID}/my_sessions"
|
||||
|
||||
module.exports = class LadderView extends RootView
|
||||
id: 'ladder-view'
|
||||
|
@ -24,9 +27,59 @@ module.exports = class LadderView extends RootView
|
|||
events:
|
||||
'click #simulate-button': 'onSimulateButtonClick'
|
||||
'click #simulate-all-button': 'onSimulateAllButtonClick'
|
||||
'click .play-button': 'onClickPlayButton'
|
||||
|
||||
constructor: (options, @levelID) ->
|
||||
super(options)
|
||||
@level = new Level(_id:@levelID)
|
||||
@level.fetch()
|
||||
@level.once 'sync', @onLevelLoaded, @
|
||||
@sessions = new LevelSessionsCollection(levelID)
|
||||
@sessions.fetch({})
|
||||
@sessions.once 'sync', @onMySessionsLoaded, @
|
||||
@simulator = new Simulator()
|
||||
@simulator.on 'statusUpdate', @updateSimulationStatus, @
|
||||
@teams = []
|
||||
|
||||
onLevelLoaded: -> @renderMaybe()
|
||||
onMySessionsLoaded: -> @renderMaybe()
|
||||
|
||||
renderMaybe: ->
|
||||
return unless @level.loaded and @sessions.loaded
|
||||
@teams = teamDataFromLevel @level
|
||||
@startsLoading = false
|
||||
@render()
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
ctx.level = @level
|
||||
ctx.link = "/play/level/#{@level.get('name')}"
|
||||
ctx.simulationStatus = @simulationStatus
|
||||
ctx.teams = @teams
|
||||
ctx.levelID = @levelID
|
||||
ctx.levelDescription = marked(@level.get('description')) if @level.get('description')
|
||||
ctx
|
||||
|
||||
afterRender: ->
|
||||
super()
|
||||
return if @startsLoading
|
||||
@insertSubView(@ladderTab = new LadderTabView({}, @level, @sessions))
|
||||
@insertSubView(@myMatchesTab = new MyMatchesTabView({}, @level, @sessions))
|
||||
setInterval(@fetchSessionsAndRefreshViews.bind(@), 10000)
|
||||
|
||||
fetchSessionsAndRefreshViews: ->
|
||||
@sessions.fetch({"success": @refreshViews})
|
||||
|
||||
refreshViews: =>
|
||||
@ladderTab.refreshLadder()
|
||||
@myMatchesTab.refreshMatches()
|
||||
console.log "refreshed views!"
|
||||
|
||||
|
||||
# Simulations
|
||||
|
||||
onSimulateAllButtonClick: (e) ->
|
||||
submitIDs = _.pluck @leaderboards[@teams[0]].topPlayers.models, "id"
|
||||
submitIDs = _.pluck @leaderboards[@teams[0].id].topPlayers.models, "id"
|
||||
for ID in submitIDs
|
||||
$.ajax
|
||||
url: '/queue/scoring'
|
||||
|
@ -58,140 +111,9 @@ module.exports = class LadderView extends RootView
|
|||
console.log "There was a problem with the named simulation status: #{e}"
|
||||
$("#simulation-status-text").text @simulationStatus
|
||||
|
||||
|
||||
constructor: (options, @levelID) ->
|
||||
super(options)
|
||||
@level = new Level(_id:@levelID)
|
||||
@level.fetch()
|
||||
@level.once 'sync', @onLevelLoaded, @
|
||||
@simulator = new Simulator()
|
||||
@simulator.on 'statusUpdate', @updateSimulationStatus, @
|
||||
|
||||
# @sessions = new LevelSessionsCollection(levelID)
|
||||
# @sessions.fetch({})
|
||||
# @sessions.once 'sync', @onMySessionsLoaded, @
|
||||
|
||||
onLevelLoaded: -> @startLoadingPhaseTwoMaybe()
|
||||
onMySessionsLoaded: ->
|
||||
@startLoadingPhaseTwoMaybe()
|
||||
|
||||
startLoadingPhaseTwoMaybe: ->
|
||||
return unless @level.loaded # and @sessions.loaded
|
||||
@loadPhaseTwo()
|
||||
|
||||
loadPhaseTwo: ->
|
||||
alliedSystem = _.find @level.get('systems'), (value) -> value.config?.teams?
|
||||
teams = []
|
||||
for teamName, teamConfig of alliedSystem.config.teams
|
||||
continue unless teamConfig.playable
|
||||
teams.push teamName
|
||||
@teams = teams
|
||||
@teamConfigs = alliedSystem.config.teams
|
||||
|
||||
@leaderboards = {}
|
||||
@challengers = {}
|
||||
for team in teams
|
||||
# teamSession = _.find @sessions.models, (session) -> session.get('team') is team
|
||||
teamSession = null
|
||||
console.log "Team session: #{JSON.stringify teamSession}"
|
||||
@leaderboards[team] = new LeaderboardData(@level, team, teamSession)
|
||||
@leaderboards[team].once 'sync', @onLeaderboardLoaded, @
|
||||
|
||||
onChallengersLoaded: -> @renderMaybe()
|
||||
onLeaderboardLoaded: -> @renderMaybe()
|
||||
|
||||
renderMaybe: ->
|
||||
loaders = _.values(@leaderboards) # .concat(_.values(@challengers))
|
||||
return unless _.every loaders, (loader) -> loader.loaded
|
||||
@startsLoading = false
|
||||
@render()
|
||||
|
||||
getRenderData: ->
|
||||
ctx = super()
|
||||
ctx.level = @level
|
||||
description = @level.get('description')
|
||||
ctx.description = if description then marked(description) else ''
|
||||
ctx.link = "/play/level/#{@level.get('name')}"
|
||||
ctx.simulationStatus = @simulationStatus
|
||||
ctx.teams = []
|
||||
ctx.levelID = @levelID
|
||||
for team in @teams or []
|
||||
otherTeam = if team is 'ogres' then 'humans' else 'ogres'
|
||||
color = @teamConfigs[team].color
|
||||
bgColor = hslToHex([color.hue, color.saturation, color.lightness + (1 - color.lightness) * 0.5])
|
||||
primaryColor = hslToHex([color.hue, 0.5, 0.5])
|
||||
ctx.teams.push({
|
||||
id: team
|
||||
name: _.string.titleize(team)
|
||||
leaderboard: @leaderboards[team]
|
||||
otherTeam: otherTeam
|
||||
bgColor: bgColor
|
||||
primaryColor: primaryColor
|
||||
})
|
||||
ctx
|
||||
|
||||
class LeaderboardData
|
||||
constructor: (@level, @team, @session) ->
|
||||
_.extend @, Backbone.Events
|
||||
@topPlayers = new LeaderboardCollection(@level, {order:-1, scoreOffset: HIGHEST_SCORE, team: @team, limit: if @session then 10 else 20})
|
||||
@topPlayers.fetch()
|
||||
@topPlayers.comparator = (model) ->
|
||||
return -model.get('totalScore')
|
||||
@topPlayers.sort()
|
||||
|
||||
@topPlayers.once 'sync', @leaderboardPartLoaded, @
|
||||
|
||||
# if @session
|
||||
# score = @session.get('totalScore') or 25
|
||||
# @playersAbove = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 4, team: @team})
|
||||
# @playersAbove.fetch()
|
||||
# @playersAbove.once 'sync', @leaderboardPartLoaded, @
|
||||
# @playersBelow = new LeaderboardCollection(@level, {order:-1, scoreOffset: score, limit: 4, team: @team})
|
||||
# @playersBelow.fetch()
|
||||
# @playersBelow.once 'sync', @leaderboardPartLoaded, @
|
||||
|
||||
leaderboardPartLoaded: ->
|
||||
if @session
|
||||
if @topPlayers.loaded # and @playersAbove.loaded and @playersBelow.loaded
|
||||
@loaded = true
|
||||
@fetchNames()
|
||||
else
|
||||
@loaded = true
|
||||
@fetchNames()
|
||||
|
||||
fetchNames: ->
|
||||
sessionCollections = [@topPlayers, @playersAbove, @playersBelow]
|
||||
sessionCollections = (s for s in sessionCollections when s)
|
||||
ids = []
|
||||
for collection in sessionCollections
|
||||
ids.push model.get('creator') for model in collection.models
|
||||
|
||||
success = (nameMap) =>
|
||||
for collection in sessionCollections
|
||||
session.set('creatorName', nameMap[session.get('creator')]) for session in collection.models
|
||||
@trigger 'sync'
|
||||
|
||||
$.ajax('/db/user/-/names', {
|
||||
data: {ids: ids}
|
||||
type: 'POST'
|
||||
success: success
|
||||
})
|
||||
|
||||
class ChallengersData
|
||||
constructor: (@level, @team, @session) ->
|
||||
_.extend @, Backbone.Events
|
||||
score = @session?.get('totalScore') or 25
|
||||
@easyPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score - 5, limit: 1, team: @team})
|
||||
@easyPlayer.fetch()
|
||||
@easyPlayer.once 'sync', @challengerLoaded, @
|
||||
@mediumPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 1, team: @team})
|
||||
@mediumPlayer.fetch()
|
||||
@mediumPlayer.once 'sync', @challengerLoaded, @
|
||||
@hardPlayer = new LeaderboardCollection(@level, {order:-1, scoreOffset: score + 5, limit: 1, team: @team})
|
||||
@hardPlayer.fetch()
|
||||
@hardPlayer.once 'sync', @challengerLoaded, @
|
||||
|
||||
challengerLoaded: ->
|
||||
if @easyPlayer.loaded and @mediumPlayer.loaded and @hardPlayer.loaded
|
||||
@loaded = true
|
||||
@trigger 'sync'
|
||||
onClickPlayButton: (e) ->
|
||||
button = $(e.target).closest('.play-button')
|
||||
teamID = button.data('team')
|
||||
session = (s for s in @sessions.models when s.get('team') is teamID)[0]
|
||||
modal = new LadderPlayModal({}, @level, session, teamID)
|
||||
@openModalView modal
|
||||
|
|
|
@ -135,23 +135,23 @@ module.exports = class HUDView extends View
|
|||
props = @$el.find('.thang-props')
|
||||
props.find(":not(.thang-name)").remove()
|
||||
props.find('.thang-name').text(if @thang.type then "#{@thang.id} - #{@thang.type}" else @thang.id)
|
||||
column = null
|
||||
for prop in @thang.hudProperties ? []
|
||||
propNames = @thang.hudProperties ? []
|
||||
nColumns = Math.ceil propNames.length / 5
|
||||
columns = ($('<div class="thang-props-column"></div>').appendTo(props) for i in [0 ... nColumns])
|
||||
for prop, i in propNames
|
||||
continue if prop is 'action'
|
||||
pel = @createPropElement prop
|
||||
continue unless pel?
|
||||
if pel.find('.bar').is('*') and props.find('.bar').is('*')
|
||||
props.find('.bar-prop').last().after pel # Keep bars together
|
||||
else
|
||||
column ?= $('<div class="thang-props-column"></div>').appendTo props
|
||||
column.append pel
|
||||
column = null if column.find('.prop').length is 5
|
||||
columns[i % nColumns].append pel
|
||||
null
|
||||
|
||||
createActions: ->
|
||||
actions = @$el.find('.thang-actions tbody').empty()
|
||||
showActions = @thang.world and not _.isEmpty(@thang.actions) and 'action' in @thang.hudProperties ? []
|
||||
@$el.find('.thang-actions').toggle showActions
|
||||
@$el.find('.thang-actions').toggleClass 'secret', showActions
|
||||
return unless showActions
|
||||
@buildActionTimespans()
|
||||
for actionName, action of @thang.actions
|
||||
|
@ -263,6 +263,7 @@ module.exports = class HUDView extends View
|
|||
labelText = prop + ": " + @formatValue(prop, val) + " / " + @formatValue(prop, max)
|
||||
if regen
|
||||
labelText += " (+" + @formatValue(prop, regen) + "/s)"
|
||||
pel.find('.bar-prop-value').text(Math.round(max)) if max
|
||||
else
|
||||
s = @formatValue(prop, val)
|
||||
labelText = "#{prop}: #{s}"
|
||||
|
|
|
@ -154,6 +154,13 @@ module.exports = class PlayView extends View
|
|||
image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png'
|
||||
description: "Strike at the weak point in an array of enemies. - by Aftermath"
|
||||
}
|
||||
{
|
||||
name: 'Bubble Sort Bootcamp Battle'
|
||||
difficulty: 3
|
||||
id: 'bubble-sort-bootcamp-battle'
|
||||
image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png'
|
||||
description: "Write a bubble sort to organize your soldiers. - by Alexandru"
|
||||
}
|
||||
{
|
||||
name: 'Enemy Artillery'
|
||||
difficulty: 1
|
||||
|
|
|
@ -261,7 +261,7 @@ module.exports = class Handler
|
|||
tv4 = require('tv4').tv4
|
||||
res = tv4.validateMultiple(input, @jsonSchema)
|
||||
res
|
||||
|
||||
|
||||
@isID: (id) -> _.isString(id) and id.length is 24 and id.match(/[a-z0-9]/gi)?.length is 24
|
||||
|
||||
getDocumentForIdOrSlug: (idOrSlug, done) ->
|
||||
|
|
|
@ -27,7 +27,7 @@ LevelHandler = class LevelHandler extends Handler
|
|||
getByRelationship: (req, res, args...) ->
|
||||
return @getSession(req, res, args[0]) if args[1] is 'session'
|
||||
return @getLeaderboard(req, res, args[0]) if args[1] is 'leaderboard'
|
||||
return @getAllSessions(req, res, args[0]) if args[1] is 'all_sessions'
|
||||
return @getMySessions(req, res, args[0]) if args[1] is 'my_sessions'
|
||||
return @getFeedback(req, res, args[0]) if args[1] is 'feedback'
|
||||
return @sendNotFoundError(res)
|
||||
|
||||
|
@ -86,26 +86,15 @@ LevelHandler = class LevelHandler extends Handler
|
|||
# associated with the handler, because the handler might return a different type
|
||||
# of model, like in this case. Refactor to move that logic to the model instead.
|
||||
|
||||
getAllSessions: (req, res, id) ->
|
||||
getMySessions: (req, res, id) ->
|
||||
@fetchLevelByIDAndHandleErrors id, req, res, (err, level) =>
|
||||
sessionQuery =
|
||||
level:
|
||||
original: level.original.toString()
|
||||
majorVersion: level.version.major
|
||||
submitted: true
|
||||
|
||||
propertiesToReturn = [
|
||||
'_id'
|
||||
'totalScore'
|
||||
'submitted'
|
||||
'team'
|
||||
'creatorName'
|
||||
]
|
||||
|
||||
query = Session
|
||||
.find(sessionQuery)
|
||||
.select(propertiesToReturn.join ' ')
|
||||
creator: req.user._id+''
|
||||
|
||||
query = Session.find(sessionQuery)
|
||||
query.exec (err, results) =>
|
||||
if err then @sendDatabaseError(res, err) else @sendSuccess res, results
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ module.exports.dispatchTaskToConsumer = (req, res) ->
|
|||
message.changeMessageVisibilityTimeout scoringTaskTimeoutInSeconds, (err) ->
|
||||
if err? then return errors.serverError res, "There was an error changing the message visibility timeout."
|
||||
console.log "Changed visibility timeout"
|
||||
constructTaskLogObject getUserIDFromRequest(req),message.getReceiptHandle(), (taskLogError, taskLogObject) ->
|
||||
constructTaskLogObject getUserIDFromRequest(req), message.getReceiptHandle(), (taskLogError, taskLogObject) ->
|
||||
if taskLogError? then return errors.serverError res, "There was an error creating the task log object."
|
||||
|
||||
taskObject.taskID = taskLogObject._id
|
||||
|
@ -107,40 +107,48 @@ module.exports.processTaskResult = (req, res) ->
|
|||
scoringTaskQueue.deleteMessage clientResponseObject.receiptHandle, (err) ->
|
||||
console.log "Deleted message."
|
||||
if err? then return errors.badInput res, "The queue message is already back in the queue, rejecting results."
|
||||
|
||||
logTaskComputation clientResponseObject, taskLog, (logErr) ->
|
||||
if logErr? then return errors.serverError res, "There as a problem logging the task computation: #{logErr}"
|
||||
|
||||
updateSessions clientResponseObject, (updateError, newScoreArray) ->
|
||||
if updateError? then return errors.serverError res, "There was an error updating the scores.#{updateError}"
|
||||
|
||||
newScoresObject = _.indexBy newScoreArray, 'id'
|
||||
|
||||
addMatchToSessions clientResponseObject, newScoresObject, (err, data) ->
|
||||
if err? then return errors.serverError res, "There was an error updating the sessions with the match! #{JSON.stringify err}"
|
||||
|
||||
originalSessionID = clientResponseObject.originalSessionID
|
||||
originalSessionTeam = clientResponseObject.originalSessionTeam
|
||||
originalSessionRank = parseInt clientResponseObject.originalSessionRank
|
||||
|
||||
determineIfSessionShouldContinueAndUpdateLog originalSessionID, originalSessionRank, (err, sessionShouldContinue) ->
|
||||
if err? then return errors.serverError res, "There was an error determining if the session should continue, #{err}"
|
||||
|
||||
if sessionShouldContinue
|
||||
opposingTeam = calculateOpposingTeam(originalSessionTeam)
|
||||
opponentID = _.pull(_.keys(newScoresObject), originalSessionID)
|
||||
sessionNewScore = newScoresObject[originalSessionID].totalScore
|
||||
opponentNewScore = newScoresObject[opponentID].totalScore
|
||||
findNearestBetterSessionID originalSessionID, sessionNewScore, opponentNewScore, opponentID ,opposingTeam, (err, opponentSessionID) ->
|
||||
if err? then return errors.serverError res, "There was an error finding the nearest sessionID!"
|
||||
unless opponentSessionID then return sendResponseObject req, res, {"message":"There were no more games to rank(game is at top!"}
|
||||
|
||||
addPairwiseTaskToQueue [originalSessionID, opponentSessionID], (err, success) ->
|
||||
if err? then return errors.serverError res, "There was an error sending the pairwise tasks to the queue!"
|
||||
sendResponseObject req, res, {"message":"The scores were updated successfully and more games were sent to the queue!"}
|
||||
else
|
||||
console.log "Player lost, achieved rank #{originalSessionRank}"
|
||||
sendResponseObject req, res, {"message":"The scores were updated successfully, person lost so no more games are being inserted!"}
|
||||
|
||||
LevelSession.findOne(_id: clientResponseObject.originalSessionID).lean().exec (err, levelSession) ->
|
||||
if err? then return errors.serverError res, "There was a problem finding the level session:#{err}"
|
||||
|
||||
supposedSubmissionDate = new Date(clientResponseObject.sessions[0].submitDate)
|
||||
|
||||
if Number(supposedSubmissionDate) isnt Number(levelSession.submitDate)
|
||||
return sendResponseObject req, res, {"message":"The game has been resubmitted. Removing from queue..."}
|
||||
|
||||
logTaskComputation clientResponseObject, taskLog, (logErr) ->
|
||||
if logErr? then return errors.serverError res, "There as a problem logging the task computation: #{logErr}"
|
||||
|
||||
updateSessions clientResponseObject, (updateError, newScoreArray) ->
|
||||
if updateError? then return errors.serverError res, "There was an error updating the scores.#{updateError}"
|
||||
|
||||
newScoresObject = _.indexBy newScoreArray, 'id'
|
||||
|
||||
addMatchToSessions clientResponseObject, newScoresObject, (err, data) ->
|
||||
if err? then return errors.serverError res, "There was an error updating the sessions with the match! #{JSON.stringify err}"
|
||||
|
||||
originalSessionID = clientResponseObject.originalSessionID
|
||||
originalSessionTeam = clientResponseObject.originalSessionTeam
|
||||
originalSessionRank = parseInt clientResponseObject.originalSessionRank
|
||||
|
||||
determineIfSessionShouldContinueAndUpdateLog originalSessionID, originalSessionRank, (err, sessionShouldContinue) ->
|
||||
if err? then return errors.serverError res, "There was an error determining if the session should continue, #{err}"
|
||||
|
||||
if sessionShouldContinue
|
||||
opposingTeam = calculateOpposingTeam(originalSessionTeam)
|
||||
opponentID = _.pull(_.keys(newScoresObject), originalSessionID)
|
||||
sessionNewScore = newScoresObject[originalSessionID].totalScore
|
||||
opponentNewScore = newScoresObject[opponentID].totalScore
|
||||
findNearestBetterSessionID originalSessionID, sessionNewScore, opponentNewScore, opponentID ,opposingTeam, (err, opponentSessionID) ->
|
||||
if err? then return errors.serverError res, "There was an error finding the nearest sessionID!"
|
||||
unless opponentSessionID then return sendResponseObject req, res, {"message":"There were no more games to rank(game is at top!"}
|
||||
|
||||
addPairwiseTaskToQueue [originalSessionID, opponentSessionID], (err, success) ->
|
||||
if err? then return errors.serverError res, "There was an error sending the pairwise tasks to the queue!"
|
||||
sendResponseObject req, res, {"message":"The scores were updated successfully and more games were sent to the queue!"}
|
||||
else
|
||||
console.log "Player lost, achieved rank #{originalSessionRank}"
|
||||
sendResponseObject req, res, {"message":"The scores were updated successfully, person lost so no more games are being inserted!"}
|
||||
|
||||
|
||||
determineIfSessionShouldContinueAndUpdateLog = (sessionID, sessionRank, cb) ->
|
||||
|
|
|
@ -13,6 +13,7 @@ module.exports.setup = (app) ->
|
|||
|
||||
fileGet = (req, res) ->
|
||||
path = req.path[6..]
|
||||
path = decodeURI path
|
||||
isFolder = false
|
||||
try
|
||||
objectId = mongoose.Types.ObjectId(path)
|
||||
|
|
|
@ -114,11 +114,19 @@ UserHandler = class UserHandler extends Handler
|
|||
ids = ids.split(',') if _.isString ids
|
||||
ids = _.uniq ids
|
||||
|
||||
# TODO: Extend and repurpose this handler to return other public info about a user more flexibly,
|
||||
# say by a query parameter that lists public properties to return.
|
||||
returnWizard = req.query.wizard or req.body.wizard
|
||||
query = if returnWizard then {name:1, wizard:1} else {name:1}
|
||||
|
||||
makeFunc = (id) ->
|
||||
(callback) ->
|
||||
User.findById(id, {name:1}).exec (err, document) ->
|
||||
User.findById(id, query).exec (err, document) ->
|
||||
return done(err) if err
|
||||
callback(null, document?.get('name') or '')
|
||||
if document and returnWizard
|
||||
callback(null, {name:document.get('name'), wizard:document.get('wizard') or {}})
|
||||
else
|
||||
callback(null, document?.get('name') or '')
|
||||
|
||||
funcs = {}
|
||||
for id in ids
|
||||
|
|
49
server_config.coffee
Normal file
|
@ -0,0 +1,49 @@
|
|||
config = {}
|
||||
|
||||
config.unittest = process.argv.indexOf("--unittest") > -1
|
||||
|
||||
config.port = process.env.COCO_PORT or process.env.COCO_NODE_PORT or 3000
|
||||
config.ssl_port = process.env.COCO_SSL_PORT or process.env.COCO_SSL_NODE_PORT or 3443
|
||||
|
||||
config.mongo =
|
||||
port: process.env.COCO_MONGO_PORT or 27017
|
||||
host: process.env.COCO_MONGO_HOST or "localhost"
|
||||
db: process.env.COCO_MONGO_DATABASE_NAME or "coco"
|
||||
mongoose_replica_string: process.env.COCO_MONGO_MONGOOSE_REPLICA_STRING or ""
|
||||
|
||||
if config.unittest
|
||||
config.port += 1
|
||||
config.ssl_port += 1
|
||||
config.mongo.host = "localhost"
|
||||
else
|
||||
config.mongo.username = process.env.COCO_MONGO_USERNAME or ""
|
||||
config.mongo.password = process.env.COCO_MONGO_PASSWORD or ""
|
||||
|
||||
config.mail =
|
||||
service: process.env.COCO_MAIL_SERVICE_NAME or "Zoho"
|
||||
username: process.env.COCO_MAIL_SERVICE_USERNAME or ""
|
||||
password: process.env.COCO_MAIL_SERVICE_PASSWORD or ""
|
||||
mailchimpAPIKey: process.env.COCO_MAILCHIMP_API_KEY or ""
|
||||
mailchimpWebhook: process.env.COCO_MAILCHIMP_WEBHOOK or "/mail/webhook"
|
||||
sendwithusAPIKey: process.env.COCO_SENDWITHUS_API_KEY or ""
|
||||
|
||||
config.queue =
|
||||
accessKeyId: process.env.COCO_AWS_ACCESS_KEY_ID or ""
|
||||
secretAccessKey: process.env.COCO_AWS_SECRET_ACCESS_KEY or ""
|
||||
region: "us-east-1"
|
||||
simulationQueueName: "simulationQueue"
|
||||
|
||||
config.mongoQueue =
|
||||
queueDatabaseName: "coco_queue"
|
||||
|
||||
config.salt = process.env.COCO_SALT or "pepper"
|
||||
config.cookie_secret = process.env.COCO_COOKIE_SECRET or "chips ahoy"
|
||||
|
||||
config.isProduction = config.mongo.host isnt "localhost"
|
||||
|
||||
if not config.unittest and not config.isProduction
|
||||
# change artificially slow down non-static requests for testing
|
||||
config.slow_down = false
|
||||
|
||||
|
||||
module.exports = config
|
|
@ -1,52 +0,0 @@
|
|||
var config = {};
|
||||
|
||||
config.unittest = process.argv.indexOf('--unittest') > -1;
|
||||
|
||||
config.port = process.env.COCO_PORT || process.env.COCO_NODE_PORT || 3000;
|
||||
config.ssl_port =
|
||||
process.env.COCO_SSL_PORT || process.env.COCO_SSL_NODE_PORT || 3443;
|
||||
|
||||
config.mongo = {};
|
||||
config.mongo.port = process.env.COCO_MONGO_PORT || 27017;
|
||||
config.mongo.host = process.env.COCO_MONGO_HOST || 'localhost';
|
||||
config.mongo.db = process.env.COCO_MONGO_DATABASE_NAME || 'coco';
|
||||
config.mongo.mongoose_replica_string = process.env.COCO_MONGO_MONGOOSE_REPLICA_STRING || '';
|
||||
|
||||
if(config.unittest) {
|
||||
config.port += 1;
|
||||
config.ssl_port += 1;
|
||||
config.mongo.host = 'localhost';
|
||||
}
|
||||
|
||||
else {
|
||||
config.mongo.username = process.env.COCO_MONGO_USERNAME || '';
|
||||
config.mongo.password = process.env.COCO_MONGO_PASSWORD || '';
|
||||
}
|
||||
|
||||
config.mail = {};
|
||||
config.mail.service = process.env.COCO_MAIL_SERVICE_NAME || "Zoho";
|
||||
config.mail.username = process.env.COCO_MAIL_SERVICE_USERNAME || "";
|
||||
config.mail.password = process.env.COCO_MAIL_SERVICE_PASSWORD || "";
|
||||
config.mail.mailchimpAPIKey = process.env.COCO_MAILCHIMP_API_KEY || '';
|
||||
config.mail.mailchimpWebhook = process.env.COCO_MAILCHIMP_WEBHOOK || '/mail/webhook';
|
||||
config.mail.sendwithusAPIKey = process.env.COCO_SENDWITHUS_API_KEY || '';
|
||||
|
||||
config.queue = {};
|
||||
config.queue.accessKeyId = process.env.COCO_AWS_ACCESS_KEY_ID || '';
|
||||
config.queue.secretAccessKey = process.env.COCO_AWS_SECRET_ACCESS_KEY || '';
|
||||
config.queue.region = 'us-east-1';
|
||||
config.queue.simulationQueueName = "simulationQueue";
|
||||
config.mongoQueue = {};
|
||||
config.mongoQueue.queueDatabaseName = "coco_queue";
|
||||
|
||||
config.salt = process.env.COCO_SALT || 'pepper';
|
||||
config.cookie_secret = process.env.COCO_COOKIE_SECRET || 'chips ahoy';
|
||||
|
||||
config.isProduction = config.mongo.host != 'localhost';
|
||||
|
||||
if(!config.unittest && !config.isProduction) {
|
||||
// change artificially slow down non-static requests for testing
|
||||
config.slow_down = false;
|
||||
}
|
||||
|
||||
module.exports = config;
|
|
@ -8,21 +8,37 @@ database = require './server/commons/database'
|
|||
baseRoute = require './server/routes/base'
|
||||
user = require './server/users/user_handler'
|
||||
logging = require './server/commons/logging'
|
||||
|
||||
config = require './server_config'
|
||||
|
||||
###Middleware setup functions implementation###
|
||||
setupRequestTimeoutMiddleware = (app) ->
|
||||
app.use (req, res, next) ->
|
||||
req.setTimeout 15000, ->
|
||||
console.log 'timed out!'
|
||||
req.abort()
|
||||
self.emit('pass',message)
|
||||
next()
|
||||
# 2014-03-03: Try not using this and see if it's still a problem
|
||||
#setupRequestTimeoutMiddleware = (app) ->
|
||||
# app.use (req, res, next) ->
|
||||
# req.setTimeout 15000, ->
|
||||
# console.log 'timed out!'
|
||||
# req.abort()
|
||||
# self.emit('pass',message)
|
||||
# next()
|
||||
|
||||
productionLogging = (tokens, req, res)->
|
||||
status = res.statusCode
|
||||
color = 32
|
||||
if status >= 500 then color = 31
|
||||
else if status >= 400 then color = 33
|
||||
else if status >= 300 then color = 36
|
||||
elapsed = (new Date()) - req._startTime
|
||||
elapsedColor = if elapsed < 500 then 90 else 31
|
||||
if (status isnt 200 and status isnt 304) or elapsed > 500
|
||||
return "\x1b[90m#{req.method} #{req.originalUrl} \x1b[#{color}m#{res.statusCode} \x1b[#{elapsedColor}m#{elapsed}ms\x1b[0m"
|
||||
null
|
||||
|
||||
setupExpressMiddleware = (app) ->
|
||||
setupRequestTimeoutMiddleware app
|
||||
app.use(express.logger('dev'))
|
||||
#setupRequestTimeoutMiddleware app
|
||||
if config.isProduction
|
||||
express.logger.format('prod', productionLogging)
|
||||
app.use(express.logger('prod'))
|
||||
else
|
||||
app.use(express.logger('dev'))
|
||||
app.use(express.static(path.join(__dirname, 'public')))
|
||||
app.use(useragent.express())
|
||||
|
||||
|
@ -31,6 +47,8 @@ setupExpressMiddleware = (app) ->
|
|||
app.use(express.bodyParser())
|
||||
app.use(express.methodOverride())
|
||||
app.use(express.cookieSession({secret:'defenestrate'}))
|
||||
if config.isProduction
|
||||
app.use(express.compress())
|
||||
|
||||
setupPassportMiddleware = (app) ->
|
||||
app.use(authentication.initialize())
|
||||
|
@ -99,6 +117,5 @@ exports.setExpressConfigurationOptions = (app) ->
|
|||
app.set('views', __dirname + '/app/views')
|
||||
app.set('view engine', 'jade')
|
||||
app.set('view options', { layout: false })
|
||||
|
||||
|
||||
|
||||
app.set('env', if config.isProduction then 'production' else 'development')
|
||||
app.set('json spaces', 0)
|
||||
|
|