mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-03-14 07:00:01 -04:00
Replayable once-per-day increasing-difficulty level basics.
This commit is contained in:
parent
61180c640d
commit
947811c01b
18 changed files with 80 additions and 13 deletions
|
@ -311,6 +311,7 @@ self.setupDebugWorldToRunUntilFrame = function (args) {
|
|||
self.debugWorld.levelSessionIDs = args.levelSessionIDs;
|
||||
self.debugWorld.submissionCount = args.submissionCount;
|
||||
self.debugWorld.flagHistory = args.flagHistory;
|
||||
self.debugWorld.difficulty = args.difficulty;
|
||||
if (args.level)
|
||||
self.debugWorld.loadFromLevel(args.level, true);
|
||||
self.debugWorld.debugging = true;
|
||||
|
@ -371,6 +372,7 @@ self.runWorld = function runWorld(args) {
|
|||
self.world.levelSessionIDs = args.levelSessionIDs;
|
||||
self.world.submissionCount = args.submissionCount;
|
||||
self.world.flagHistory = args.flagHistory || [];
|
||||
self.world.difficulty = args.difficulty || 0;
|
||||
if(args.level)
|
||||
self.world.loadFromLevel(args.level, true);
|
||||
self.world.preloading = args.preload;
|
||||
|
|
|
@ -39,7 +39,7 @@ module.exports = class Angel extends CocoClass
|
|||
|
||||
# say: debugging stuff, usually off; log: important performance indicators, keep on
|
||||
say: (args...) -> #@log args...
|
||||
log: ->
|
||||
log: ->
|
||||
# console.info.apply is undefined in IE9, CofeeScript splats invocation won't work.
|
||||
# http://stackoverflow.com/questions/5472938/does-ie9-support-console-log-and-is-it-a-real-function
|
||||
message = "|#{@shared.godNick}'s #{@nick}|"
|
||||
|
@ -246,6 +246,7 @@ module.exports = class Angel extends CocoClass
|
|||
work.testWorld.levelSessionIDs = work.levelSessionIDs
|
||||
work.testWorld.submissionCount = work.submissionCount
|
||||
work.testWorld.flagHistory = work.flagHistory ? []
|
||||
work.testWorld.difficulty = work.difficulty
|
||||
testWorld.loadFromLevel work.level
|
||||
work.testWorld.preloading = work.preload
|
||||
work.testWorld.headless = work.headless
|
||||
|
|
|
@ -63,6 +63,7 @@ module.exports = class God extends CocoClass
|
|||
onTomeCast: (e) ->
|
||||
@lastSubmissionCount = e.submissionCount
|
||||
@lastFlagHistory = (flag for flag in e.flagHistory when flag.source isnt 'code')
|
||||
@lastDifficulty = e.difficulty
|
||||
@createWorld e.spells, e.preload, e.realTime
|
||||
|
||||
createWorld: (spells, preload, realTime) ->
|
||||
|
@ -92,6 +93,7 @@ module.exports = class God extends CocoClass
|
|||
levelSessionIDs: @levelSessionIDs
|
||||
submissionCount: @lastSubmissionCount
|
||||
flagHistory: @lastFlagHistory
|
||||
difficulty: @lastDifficulty
|
||||
goals: @angelsShare.goalManager?.getGoals()
|
||||
headless: @angelsShare.headless
|
||||
preload: preload
|
||||
|
@ -123,6 +125,7 @@ module.exports = class God extends CocoClass
|
|||
levelSessionIDs: @levelSessionIDs
|
||||
submissionCount: @lastSubmissionCount
|
||||
flagHistory: @lastFlagHistory
|
||||
difficulty: @lastDifficulty
|
||||
goals: @goalManager?.getGoals()
|
||||
frame: args.frame
|
||||
currentThangID: args.thangID
|
||||
|
|
|
@ -374,6 +374,7 @@ module.exports = class LevelLoader extends CocoClass
|
|||
@world.levelSessionIDs = if @opponentSessionID then [@sessionID, @opponentSessionID] else [@sessionID]
|
||||
@world.submissionCount = @session?.get('state')?.submissionCount ? 0
|
||||
@world.flagHistory = @session?.get('state')?.flagHistory ? []
|
||||
@world.difficulty = @session?.get('state')?.difficulty ? 0
|
||||
serializedLevel = @level.serialize(@supermodel, @session, @opponentSession)
|
||||
@world.loadFromLevel serializedLevel, false
|
||||
console.log 'World has been initialized from level loader.'
|
||||
|
|
|
@ -360,7 +360,7 @@ module.exports = class World
|
|||
#console.log "... world serializing frames from", startFrame, "to", endFrame, "of", @totalFrames
|
||||
[transferableObjects, nontransferableObjects] = [0, 0]
|
||||
delete flag.processed for flag in @flagHistory
|
||||
o = {totalFrames: @totalFrames, maxTotalFrames: @maxTotalFrames, frameRate: @frameRate, dt: @dt, victory: @victory, userCodeMap: {}, trackedProperties: {}, flagHistory: @flagHistory}
|
||||
o = {totalFrames: @totalFrames, maxTotalFrames: @maxTotalFrames, frameRate: @frameRate, dt: @dt, victory: @victory, userCodeMap: {}, trackedProperties: {}, flagHistory: @flagHistory, difficulty: @difficulty}
|
||||
o.trackedProperties[prop] = @[prop] for prop in @trackedProperties or []
|
||||
|
||||
for thangID, methods of @userCodeMap
|
||||
|
@ -467,7 +467,7 @@ module.exports = class World
|
|||
w.userCodeMap[thangID][methodName][aetherStateKey] = serializedAether[aetherStateKey]
|
||||
else
|
||||
w = new World o.userCodeMap, classMap
|
||||
[w.totalFrames, w.maxTotalFrames, w.frameRate, w.dt, w.scriptNotes, w.victory, w.flagHistory] = [o.totalFrames, o.maxTotalFrames, o.frameRate, o.dt, o.scriptNotes ? [], o.victory, o.flagHistory]
|
||||
[w.totalFrames, w.maxTotalFrames, w.frameRate, w.dt, w.scriptNotes, w.victory, w.flagHistory, w.difficulty] = [o.totalFrames, o.maxTotalFrames, o.frameRate, o.dt, o.scriptNotes ? [], o.victory, o.flagHistory, o.difficulty]
|
||||
w[prop] = val for prop, val of o.trackedProperties
|
||||
|
||||
perf.t1 = now()
|
||||
|
|
|
@ -53,3 +53,16 @@ module.exports = class LevelSession extends CocoModel
|
|||
save: (attrs, options) ->
|
||||
return if @shouldAvoidCorruptData attrs
|
||||
super attrs, options
|
||||
|
||||
increaseDifficulty: ->
|
||||
state = @get('state') ? {}
|
||||
state.difficulty = (state.difficulty ? 0) + 1
|
||||
delete state.lastUnsuccessfulSubmissionTime
|
||||
@set 'state', state
|
||||
|
||||
timeUntilResubmit: ->
|
||||
state = @get('state') ? {}
|
||||
return 0 unless last = state.lastUnsuccessfulSubmissionTime
|
||||
last = new Date(last) if _.isString last
|
||||
# Wait at least this long before allowing submit button active again.
|
||||
(last - new Date()) + 22 * 60 * 60 * 1000
|
||||
|
|
|
@ -299,6 +299,7 @@ _.extend LevelSchema.properties,
|
|||
style: c.shortString title: 'Style', description: 'Like: original, eccentric, scripted, edited, etc.'
|
||||
free: {type: 'boolean', title: 'Free', description: 'Whether this video is freely available to all players without a subscription.'}
|
||||
url: c.url {title: 'URL', description: 'Link to the video on Vimeo.'}
|
||||
replayable: {type: 'boolean', title: 'Replayable', description: 'Whether this (hero) level infinitely scales up its difficulty and can be beaten over and over for greater rewards.'}
|
||||
|
||||
# Admin flags
|
||||
adventurer: { type: 'boolean' }
|
||||
|
|
|
@ -114,6 +114,12 @@ _.extend LevelSessionSchema.properties,
|
|||
description: 'How many times the session has been submitted for real-time playback (can affect the random seed).'
|
||||
type: 'integer'
|
||||
minimum: 0
|
||||
difficulty:
|
||||
description: 'The highest difficulty level beaten, for use in increasing-difficulty replayable levels.'
|
||||
type: 'integer'
|
||||
minimum: 0
|
||||
lastUnsuccessfulSubmissionTime: c.date
|
||||
description: 'The last time that real-time submission was started without resulting in a win.'
|
||||
flagHistory:
|
||||
description: 'The history of flag events during the last real-time playback submission.'
|
||||
type: 'array'
|
||||
|
|
|
@ -7,16 +7,20 @@ module.exports =
|
|||
preload: {type: 'boolean'}
|
||||
realTime: {type: 'boolean'}
|
||||
|
||||
'tome:cast-spells': c.object {title: 'Cast Spells', description: 'Published when spells are cast', required: ['spells', 'preload', 'realTime', 'submissionCount', 'flagHistory']},
|
||||
'tome:cast-spells': c.object {title: 'Cast Spells', description: 'Published when spells are cast', required: ['spells', 'preload', 'realTime', 'submissionCount', 'flagHistory', 'difficulty']},
|
||||
spells: [type: 'object']
|
||||
preload: [type: 'boolean']
|
||||
realTime: [type: 'boolean']
|
||||
submissionCount: [type: 'integer']
|
||||
flagHistory: [type: 'array']
|
||||
difficulty: [type: 'integer']
|
||||
|
||||
'tome:manual-cast': c.object {title: 'Manually Cast Spells', description: 'Published when you wish to manually recast all spells', required: []},
|
||||
realTime: {type: 'boolean'}
|
||||
|
||||
'tome:manual-cast-denied': c.object {title: 'Manual Cast Denied', description: 'Published when player attempts to submit for real-time playback, but must wait after a replayable level failure.', required: ['timeUntilResubmit']},
|
||||
timeUntilResubmit: {type: 'number'}
|
||||
|
||||
'tome:spell-created': c.object {title: 'Spell Created', description: 'Published after a new spell has been created', required: ['spell']},
|
||||
spell: {type: 'object'}
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
button.btn.btn-lg.btn-illustrated.cast-button(title=castVerbose)
|
||||
span(data-i18n="play_level.tome_run_button_ran") Ran
|
||||
|
||||
button.btn.btn-lg.btn-illustrated.submit-button(title=castRealTimeVerbose, data-i18n="play_level.tome_submit_button") Submit
|
||||
button.btn.btn-lg.btn-illustrated.submit-button(title=castRealTimeVerbose)
|
||||
span(data-i18n="play_level.tome_submit_button") Submit
|
||||
span.spl.secret.submit-again-time
|
||||
|
||||
button.btn.btn-lg.btn-illustrated.done-button.secret
|
||||
span(data-i18n="play_level.done") Done
|
||||
|
|
|
@ -63,7 +63,7 @@ module.exports = class AchievementEditView extends RootView
|
|||
@$el.find('#achievement-view').empty()
|
||||
for key, value of @treema.data
|
||||
@achievement.set key, value
|
||||
earned = earnedPoints: @achievement.get 'worth'
|
||||
earned = get: (key) => {earnedPoints: @achievement.get('worth'), previouslyAchievedAmount: 0}[key]
|
||||
popup = new AchievementPopup achievement: @achievement, earnedAchievement: earned, popup: false, container: $('#achievement-view')
|
||||
|
||||
openSaveModal: ->
|
||||
|
|
|
@ -15,7 +15,7 @@ module.exports = class SettingsTabView extends CocoView
|
|||
editableSettings: [
|
||||
'name', 'description', 'documentation', 'nextLevel', 'background', 'victory', 'i18n', 'icon', 'goals',
|
||||
'type', 'terrain', 'showsGuide', 'banner', 'employerDescription', 'loadingTip', 'requiresSubscription',
|
||||
'tasks', 'helpVideos'
|
||||
'tasks', 'helpVideos', 'replayable'
|
||||
]
|
||||
|
||||
subscriptions:
|
||||
|
|
|
@ -520,6 +520,7 @@ module.exports = class PlayLevelView extends RootView
|
|||
return if @destroyed
|
||||
# TODO: Show a victory dialog specific to hero-ladder level
|
||||
if @goalManager.checkOverallStatus() is 'success' and not @options.realTimeMultiplayerSessionID?
|
||||
@session.increaseDifficulty() if @level.get 'replayable'
|
||||
Backbone.Mediator.publish 'level:show-victory', showModal: true
|
||||
|
||||
destroy: ->
|
||||
|
|
|
@ -14,6 +14,7 @@ module.exports = class CastButtonView extends CocoView
|
|||
subscriptions:
|
||||
'tome:spell-changed': 'onSpellChanged'
|
||||
'tome:cast-spells': 'onCastSpells'
|
||||
'tome:manual-cast-denied': 'onManualCastDenied'
|
||||
'god:new-world-created': 'onNewWorld'
|
||||
'real-time-multiplayer:created-game': 'onJoinedRealTimeMultiplayerGame'
|
||||
'real-time-multiplayer:joined-game': 'onJoinedRealTimeMultiplayerGame'
|
||||
|
@ -25,6 +26,11 @@ module.exports = class CastButtonView extends CocoView
|
|||
super options
|
||||
@spells = options.spells
|
||||
@castShortcut = '⇧↵'
|
||||
@updateReplayabilityInterval = setInterval @updateReplayability, 1000
|
||||
|
||||
destroy: ->
|
||||
clearInterval @updateReplayabilityInterval
|
||||
super()
|
||||
|
||||
getRenderData: (context={}) ->
|
||||
context = super context
|
||||
|
@ -49,6 +55,7 @@ module.exports = class CastButtonView extends CocoView
|
|||
@$el.find('.done-button').show()
|
||||
if @options.level.get('slug') is 'thornbush-farm'# and not @options.session.get('state')?.complete
|
||||
@$el.find('.submit-button').hide() # Hide submit until first win so that script can explain it.
|
||||
@updateReplayability()
|
||||
|
||||
attachTo: (spellView) ->
|
||||
@$el.detach().prependTo(spellView.toolbarView.$el).show()
|
||||
|
@ -59,8 +66,11 @@ module.exports = class CastButtonView extends CocoView
|
|||
onCastRealTimeButtonClick: (e) ->
|
||||
if @inRealTimeMultiplayerSession
|
||||
Backbone.Mediator.publish 'real-time-multiplayer:manual-cast', {}
|
||||
else if @options.level.get('replayable') and (timeUntilResubmit = @options.session.timeUntilResubmit()) > 0
|
||||
Backbone.Mediator.publish 'tome:manual-cast-denied', timeUntilResubmit: timeUntilResubmit
|
||||
else
|
||||
Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}
|
||||
@updateReplayability()
|
||||
|
||||
onDoneButtonClick: (e) ->
|
||||
Backbone.Mediator.publish 'level:show-victory', showModal: true
|
||||
|
@ -72,14 +82,19 @@ module.exports = class CastButtonView extends CocoView
|
|||
return if e.preload
|
||||
@casting = true
|
||||
if @hasStartedCastingOnce # Don't play this sound the first time
|
||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'cast', volume: 0.5
|
||||
@playSound 'cast', 0.5
|
||||
@hasStartedCastingOnce = true
|
||||
@updateCastButton()
|
||||
|
||||
onManualCastDenied: (e) ->
|
||||
wait = moment().add(e.timeUntilResubmit, 'ms').fromNow()
|
||||
#@playSound 'manual-cast-denied', 1.0 # find some sound for this?
|
||||
noty text: "You can try again #{wait}.", layout: 'center', type: 'warning', killer: false, timeout: 6000
|
||||
|
||||
onNewWorld: (e) ->
|
||||
@casting = false
|
||||
if @hasCastOnce # Don't play this sound the first time
|
||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'cast-end', volume: 0.5
|
||||
@playSound 'cast-end', 0.5
|
||||
@hasCastOnce = true
|
||||
@updateCastButton()
|
||||
|
||||
|
@ -120,6 +135,17 @@ module.exports = class CastButtonView extends CocoView
|
|||
@castButton.text castText
|
||||
#@castButton.prop 'disabled', not castable
|
||||
|
||||
updateReplayability: =>
|
||||
return if @destroyed
|
||||
return unless @options.level.get 'replayable'
|
||||
timeUntilResubmit = @options.session.timeUntilResubmit()
|
||||
disabled = timeUntilResubmit > 0
|
||||
submitButton = @$el.find('.submit-button').toggleClass('disabled', disabled)
|
||||
submitAgainLabel = submitButton.find('.submit-again-time').toggleClass('secret', not disabled)
|
||||
if disabled
|
||||
waitTime = moment().add(timeUntilResubmit, 'ms').fromNow()
|
||||
submitAgainLabel.text waitTime
|
||||
|
||||
setAutocastDelay: (delay) ->
|
||||
#console.log 'Set autocast delay to', delay
|
||||
return unless delay
|
||||
|
|
|
@ -122,7 +122,11 @@ module.exports = class SpellView extends CocoView
|
|||
addCommand
|
||||
name: 'run-code-real-time'
|
||||
bindKey: {win: 'Ctrl-Shift-Enter', mac: 'Command-Shift-Enter|Ctrl-Shift-Enter'}
|
||||
exec: -> Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}
|
||||
exec: =>
|
||||
if @options.level.get('replayable') and (timeUntilResubmit = @session.timeUntilResubmit()) > 0
|
||||
Backbone.Mediator.publish 'tome:manual-cast-denied', timeUntilResubmit: timeUntilResubmit
|
||||
else
|
||||
Backbone.Mediator.publish 'tome:manual-cast', {realTime: true}
|
||||
addCommand
|
||||
name: 'no-op'
|
||||
bindKey: {win: 'Ctrl-S', mac: 'Command-S|Ctrl-S'}
|
||||
|
@ -616,7 +620,7 @@ module.exports = class SpellView extends CocoView
|
|||
onSignificantChange.push _.debounce @checkSuspectCode, 750 if @options.level.get 'suspectCode'
|
||||
@onCodeChangeMetaHandler = =>
|
||||
return if @eventsSuppressed
|
||||
Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'code-change', volume: 0.5
|
||||
#@playSound 'code-change', volume: 0.5 # Currently not using this sound.
|
||||
if @spellThang
|
||||
@spell.hasChangedSignificantly @getSource(), @spellThang.aether.raw, (hasChanged) =>
|
||||
if not @spellThang or hasChanged
|
||||
|
|
|
@ -163,8 +163,9 @@ module.exports = class TomeView extends CocoView
|
|||
if realTime
|
||||
sessionState.submissionCount = (sessionState.submissionCount ? 0) + 1
|
||||
sessionState.flagHistory = _.filter sessionState.flagHistory ? [], (event) => event.team isnt (@options.session.get('team') ? 'humans')
|
||||
sessionState.lastUnsuccessfulSubmissionTime = new Date() if @options.level.get 'replayable'
|
||||
@options.session.set 'state', sessionState
|
||||
Backbone.Mediator.publish 'tome:cast-spells', spells: @spells, preload: preload, realTime: realTime, submissionCount: sessionState.submissionCount ? 0, flagHistory: sessionState.flagHistory ? []
|
||||
Backbone.Mediator.publish 'tome:cast-spells', spells: @spells, preload: preload, realTime: realTime, submissionCount: sessionState.submissionCount ? 0, flagHistory: sessionState.flagHistory ? [], difficulty: sessionState.difficulty ? 0
|
||||
|
||||
onToggleSpellList: (e) ->
|
||||
@spellList.rerenderEntries()
|
||||
|
|
|
@ -80,6 +80,7 @@ work = () ->
|
|||
self.world.levelSessionIDs = args.levelSessionIDs
|
||||
self.world.submissionCount = args.submissionCount
|
||||
self.world.flagHistory = args.flagHistory
|
||||
self.world.difficulty = args.difficulty
|
||||
self.world.loadFromLevel args.level, true if args.level
|
||||
self.world.headless = args.headless
|
||||
self.goalManager = new GoalManager(self.world)
|
||||
|
|
|
@ -55,6 +55,7 @@ LevelHandler = class LevelHandler extends Handler
|
|||
'tasks'
|
||||
'helpVideos'
|
||||
'campaign'
|
||||
'replayable'
|
||||
]
|
||||
|
||||
postEditableProperties: ['name']
|
||||
|
@ -383,7 +384,7 @@ LevelHandler = class LevelHandler extends Handler
|
|||
# Build list of level average playtimes
|
||||
playtimes = []
|
||||
for item in data
|
||||
playtimes.push
|
||||
playtimes.push
|
||||
level: item._id.level
|
||||
created: item._id.created
|
||||
average: item.average
|
||||
|
|
Loading…
Reference in a new issue