diff --git a/app/styles/terrain_randomise.sass b/app/styles/terrain_randomise.sass new file mode 100644 index 000000000..e6cf29bec --- /dev/null +++ b/app/styles/terrain_randomise.sass @@ -0,0 +1,128 @@ +#terrain-randomise-modal + + .choose-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 + + .choose-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 + + .preset-size + border-left: 15px solid transparent + right: 0 + //text-align: right + span + right: 3px + + .preset-name + 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 .preset-name + border-top: 25px solid limegreen + + .medium-option .preset-name + border-top: 25px solid darkorange + + .hard-option .preset-name + border-top: 25px solid black + color: white \ No newline at end of file diff --git a/app/templates/editor/level/edit.jade b/app/templates/editor/level/edit.jade index d04b8f4bd..e446f7613 100644 --- a/app/templates/editor/level/edit.jade +++ b/app/templates/editor/level/edit.jade @@ -79,6 +79,8 @@ block header a(data-i18n="common.fork")#fork-level-start-button Fork li(class=anonymous ? "disabled": "") a(data-toggle="coco-modal", data-target="modal/revert", data-i18n="editor.revert")#revert-button Revert + li(class=anonymous ? "disabled": "") + a(data-toggle="coco-modal", data-target="modal/terrain_randomise", data-i18n="editor.randomise")#randomise-button Randomise li(class=anonymous ? "disabled": "") a(data-i18n="editor.pop_i18n")#pop-level-i18n-button Populate i18n li.divider diff --git a/app/templates/modal/terrain_randomise.jade b/app/templates/modal/terrain_randomise.jade new file mode 100644 index 000000000..680af187c --- /dev/null +++ b/app/templates/modal/terrain_randomise.jade @@ -0,0 +1,14 @@ +extends /templates/modal/modal_base + +block modal-header-content + h3(data-i18n="editor.pick_a_terrain") Pick a Terrain + +block modal-body-content + div#normal-view + a(href="#") + div.choose-option(data-preset-type="grassy", data-preset-size="small") + div.preset-size.name-label + span(data-i18n="ladder.small") Small + div.preset-name + span(data-i18n="ladder.grassy") Grassy + //- for model in models \ No newline at end of file diff --git a/app/views/editor/level/thangs_tab_view.coffee b/app/views/editor/level/thangs_tab_view.coffee index 8e905654a..010b38b50 100644 --- a/app/views/editor/level/thangs_tab_view.coffee +++ b/app/views/editor/level/thangs_tab_view.coffee @@ -43,6 +43,7 @@ module.exports = class ThangsTabView extends View 'sprite:mouse-up': 'onSpriteMouseUp' 'sprite:double-clicked': 'onSpriteDoubleClicked' 'surface:stage-mouse-up': 'onStageMouseUp' + 'randomise:terrain-generated': 'onRandomiseTerrain' events: 'click #extant-thangs-filter button': 'onFilterExtantThangs' @@ -57,6 +58,8 @@ module.exports = class ThangsTabView extends View 'delete, del, backspace': 'deleteSelectedExtantThang' 'left': -> @moveAddThangSelection -1 'right': -> @moveAddThangSelection 1 + 'ctrl+z': 'undoAction' + 'ctrl+shift+z': 'redoAction' constructor: (options) -> super options @@ -221,6 +224,11 @@ module.exports = class ThangsTabView extends View return unless e.thang @editThang thangID: e.thang.id + onRandomiseTerrain: (e) -> + for thang in e.thangs + @selectAddThangType thang.id + @addThang @addThangType, thang.pos + # TODO: figure out a good way to have all Surface clicks and Treema clicks just proxy in one direction, so we can maintain only one way of handling selection and deletion onExtantThangSelected: (e) -> @selectedExtantSprite?.setNameLabel? null unless @selectedExtantSprite is e.sprite @@ -450,6 +458,12 @@ module.exports = class ThangsTabView extends View $('#add-thangs-column').toggle() @onWindowResize e + undoAction: (e) -> + @thangsTreema.undo() + + redoAction: (e) -> + @thangsTreema.redo() + class ThangsNode extends TreemaNode.nodeMap.array valueClass: 'treema-array-replacement' getChildren: -> diff --git a/app/views/modal/terrain_randomise_modal.coffee b/app/views/modal/terrain_randomise_modal.coffee new file mode 100644 index 000000000..94b4676fb --- /dev/null +++ b/app/views/modal/terrain_randomise_modal.coffee @@ -0,0 +1,187 @@ +ModalView = require 'views/kinds/ModalView' +template = require 'templates/modal/terrain_randomise' +CocoModel = require 'models/CocoModel' + +clusters = { + 'rocks': ['Rock 1', 'Rock 2', 'Rock 3', 'Rock 4', 'Rock 5', 'Rock Cluster 1', 'Rock Cluster 2', 'Rock Cluster 3'] + 'trees': ['Tree 1', 'Tree 2', 'Tree 3', 'Tree 4'] + 'shrubs': ['Shrub 1', 'Shrub 2', 'Shrub 3'] + 'houses': ['House 1', 'House 2', 'House 3', 'House 4'] + 'animals': ['Cow', 'Horse'] + 'wood': ['Firewood 1', 'Firewood 2', 'Firewood 3', 'Barrel'] + 'farm': ['Farm'] +} + +presets = { + # 'dungeon': { + # 'type':'dungeon' + # 'borders':['Dungeon Wall'] + # 'floors':['Dungeon Floor'] + # 'decorations':[] + # } + 'grassy': { + 'type':'grassy' + 'borders':['Tree 1', 'Tree 2', 'Tree 3'] + 'floors':['Grass01', 'Grass02', 'Grass03'] + 'decorations': { + 'house': { + 'num':[1,2] #min-max + 'width': 20 + 'height': 20 + 'clusters': { + 'houses':[1,1] + 'trees':[1,2] + 'shrubs':[0,3] + 'rocks':[1,2] + } + } + 'farm': { + 'num':[1,2] #min-max + 'width': 20 + 'height': 20 + 'clusters': { + 'farm':[1,1] + 'shrubs':[2,3] + 'wood':[2,4] + 'animals':[2,3] + } + } + } + } +} + +sizes = { + 'small': { + 'x':80 + 'y':68 + } + 'large': { + 'x':160 + 'y':136 + } + 'floorSize': { + 'x':20 + 'y':20 + } + 'borderSize': { + 'x':4 + 'y':4 + } +} + +module.exports = class TerrainRandomiseModal extends ModalView + id: 'terrain-randomise-modal' + template: template + thangs = [] + + events: + 'click .choose-option': 'onRandomise' + + onRevertModel: (e) -> + id = $(e.target).val() + CocoModel.backedUp[id].revert() + $(e.target).closest('tr').remove() + @reloadOnClose = true + + onRandomise: (e) -> + target = $(e.target) + presetType = target.attr 'data-preset-type' + presetSize = target.attr 'data-preset-size' + @randomiseThangs presetType, presetSize + Backbone.Mediator.publish('randomise:terrain-generated', + 'thangs': @thangs + ) + + randomiseThangs: (presetName, presetSize) -> + preset = presets[presetName] + presetSize = sizes[presetSize] + @thangs = [] + @randomiseFloor preset, presetSize + @randomiseBorder preset, presetSize + @randomiseDecorations preset, presetSize + + randomiseFloor: (preset, presetSize) -> + for i in _.range(0, presetSize.x, sizes.floorSize.x) + for j in _.range(0, presetSize.y, sizes.floorSize.y) + @thangs.push { + 'id': @getRandomThang(preset.floors) + 'pos': { + 'x': i + 'y': j + } + } + + randomiseBorder: (preset, presetSize) -> + for i in _.range(0-sizes.floorSize.x/2+sizes.borderSize.x, presetSize.x-sizes.floorSize.x/2, sizes.borderSize.x) + @thangs.push { + 'id': @getRandomThang(preset.borders) + 'pos': { + 'x': i + 'y': 0-sizes.floorSize.x/2 + } + } + @thangs.push { + 'id': @getRandomThang(preset.borders) + 'pos': { + 'x': i + 'y': presetSize.y - sizes.borderSize.y + } + } + + for i in _.range(0-sizes.floorSize.y/2, presetSize.y-sizes.borderSize.y, sizes.borderSize.y) + @thangs.push { + 'id': @getRandomThang(preset.borders) + 'pos': { + 'x': 0-sizes.floorSize.x/2+sizes.borderSize.x + 'y': i + } + } + @thangs.push { + 'id': @getRandomThang(preset.borders) + 'pos': { + 'x': presetSize.x - sizes.borderSize.x - sizes.floorSize.x/2 + 'y': i + } + } + + randomiseDecorations: (preset, presetSize)-> + for name, decoration of preset.decorations + for num in _.range(_.random(decoration.num[0], decoration.num[1])) + center = + { + 'x':_.random(decoration.width, presetSize.x - decoration.width), + 'y':_.random(decoration.height, presetSize.y - decoration.height) + } + min = + { + 'x':center.x - decoration.width/2 + 'y':center.y - decoration.height/2 + } + max = + { + 'x':center.x + decoration.width/2 + 'y':center.y + decoration.height/2 + } + for cluster, range of decoration.clusters + for i in _.range(_.random(range[0], range[1])) + @thangs.push { + 'id':@getRandomThang(clusters[cluster]) + 'pos':{ + 'x':_.random(min.x, max.x) + 'y':_.random(min.y, max.y) + } + } + + + getRandomThang: (thangList) -> + return thangList[_.random(0, thangList.length-1)] + + getRenderData: -> + c = super() + models = _.values CocoModel.backedUp + models = (m for m in models when m.hasLocalChanges()) + c.models = models + c + + onHidden: -> + location.reload() if @reloadOnClose