diff --git a/js/GraphicSet.js b/js/GraphicSet.js index c48c70f..b1845cd 100644 --- a/js/GraphicSet.js +++ b/js/GraphicSet.js @@ -6,7 +6,8 @@ let GraphicSet = [ scale: 3, backgroundColor: '#6096ff', backgroundImage: null, - tilePreview: 5 + tilePreview: 5, + primaryTiles: 8, }, { name: 'Moon', @@ -15,7 +16,8 @@ let GraphicSet = [ scale: 3, backgroundColor: 'black', backgroundImage: 'background_earth.jpg', - tilePreview: 1 + tilePreview: 1, + primaryTiles: 2, }, { name: 'Death Star', @@ -24,7 +26,8 @@ let GraphicSet = [ scale: 1, backgroundColor: '#171721', backgroundImage: null, - tilePreview: 1 + tilePreview: 1, + primaryTiles: 6, } ]; diff --git a/js/Level.js b/js/Level.js index d7f2757..370f139 100644 --- a/js/Level.js +++ b/js/Level.js @@ -79,7 +79,7 @@ export default class Level let loader = new FileLoader(filename); loader.onLoad = (data) => { const json = JSON.parse(data); - const level = new Level(Terrain.createFromJson(json)); + const level = new Level(Terrain.createFromJson(json)); level.setGravity(json.gravity); callback(level); diff --git a/tilorswift/js/Brush.js b/tilorswift/js/Brush.js index c4cd1b2..85abf16 100644 --- a/tilorswift/js/Brush.js +++ b/tilorswift/js/Brush.js @@ -5,5 +5,6 @@ export default class Brush constructor() { this.mode = BrushMode.TERRAIN; + this.isIntelligent = false; } -} \ No newline at end of file +} diff --git a/tilorswift/js/ButtonTile.js b/tilorswift/js/ButtonTile.js index 51dc7ed..8d68da7 100644 --- a/tilorswift/js/ButtonTile.js +++ b/tilorswift/js/ButtonTile.js @@ -5,7 +5,7 @@ export default class ButtonTile extends Field { constructor(tileset, index = 0) { - super(tileset, index); + super(tileset, 0, 0, index); } initHtml() { @@ -22,4 +22,4 @@ export default class ButtonTile extends Field } ) } -} \ No newline at end of file +} diff --git a/tilorswift/js/Field.js b/tilorswift/js/Field.js index e9d1a55..f12cc3d 100644 --- a/tilorswift/js/Field.js +++ b/tilorswift/js/Field.js @@ -3,10 +3,12 @@ import TilorswiftFieldEnteredEvent from "./events/TilorswiftFieldEnteredEvent.js export default class Field { - constructor(tileset, index = -1) + constructor(tileset, x = 0, y = 0, index = -1) { this.tileset = tileset; this.index = index; + this.x = x; + this.y = y; this.isEntrancePoint = false; this.isTargetPoint = false; this.initHtml(); @@ -91,4 +93,4 @@ export default class Field { return this.htmlElement; } -} \ No newline at end of file +} diff --git a/tilorswift/js/Terrain.js b/tilorswift/js/Terrain.js index 99566c3..3ee6ee5 100644 --- a/tilorswift/js/Terrain.js +++ b/tilorswift/js/Terrain.js @@ -75,14 +75,13 @@ export default class Terrain _insertRow(index = undefined) { - let row = []; - let tr = document.createElement('tr'); + const row = []; + const tr = document.createElement('tr'); for (let col = 0; col < this.tilesX; col++) { - let field = new Field(this.tileset); - let td = field.getElement(); + const field = new Field(this.tileset, col, this.fields.length); row.push(field); - tr.appendChild(td); + tr.appendChild(field.getElement()); } if (index === undefined || index >= this.tilesY - 1) { @@ -183,10 +182,10 @@ export default class Terrain static createFromJson(levelData) { - let graphicSet = GraphicSet[levelData.tileset]; + const graphicSet = GraphicSet[levelData.tileset]; - let tileset = new Tileset(levelData.tileset); - let terrain = new Terrain(tileset, levelData.columns, levelData.rows, graphicSet.backgroundColor); + const tileset = new Tileset(levelData.tileset); + const terrain = new Terrain(tileset, levelData.columns, levelData.rows, graphicSet.backgroundColor); terrain.backgroundImage = graphicSet.backgroundImage ?? undefined; for (let y = 0; y < levelData.rows; y++) { @@ -203,4 +202,60 @@ export default class Terrain return terrain; } + + getFieldNeighbours(field) + { + const neighbours = []; + + for (let x = Math.max(0, field.x - 1); x < Math.min(this.tilesX, field.x + 2); x++) { + for (let y = Math.max(0, field.y - 1); y < Math.min(this.tilesY, field.y + 2); y++) { + if (field.x === x && field.y === y) { + continue; + } + + neighbours.push(this.fields[y][x]); + } + } + + return neighbours; + } + + hasFieldNeighbour(field, offsetX, offsetY) + { + const x = field.x + offsetX; + const y = field.y + offsetY; + + if (x < 0 || x > this.tilesX - 1) { + return true; + } + + if (y < 0 || y > this.tilesY - 1) { + return true; + } + + return this.fields[y][x].index > -1; + } + + getFieldNeighbourCode(field) + { + let code = ''; + + if (!this.hasFieldNeighbour(field, -1, 0)) { + code += 'l'; + } + + if (!this.hasFieldNeighbour(field, 0, -1)) { + code += 't'; + } + + if (!this.hasFieldNeighbour(field, 1, 0)) { + code += 'r'; + } + + if (!this.hasFieldNeighbour(field, 0, 1)) { + code += 'b'; + } + + return code; + } } diff --git a/tilorswift/js/Tileset.js b/tilorswift/js/Tileset.js index 863d951..5c4cb32 100644 --- a/tilorswift/js/Tileset.js +++ b/tilorswift/js/Tileset.js @@ -10,6 +10,7 @@ export default class Tileset this.image.src = '../' + Setting.TILESET_LOCATION + GraphicSet[this.setId].tileset; this.tiles = GraphicSet[this.setId].tiles; this.scale = GraphicSet[this.setId].scale; + this.primaryTiles = GraphicSet[this.setId].primaryTiles; } getWidth() @@ -31,4 +32,16 @@ export default class Tileset { return this.image.height * this.scale; } + + hasExtendedTiles() + { + return GraphicSet[this.setId].tiles > GraphicSet[this.setId].primaryTiles ; + } + + getTileIndexFactor(code) + { + const CODES = ['ltr', 't', 'r', 'b', 'l', 'lt', 'tr', 'ltb', 'tb', 'trb', 'lb', 'rb', 'ltrb', 'lr', 'lrb']; + + return CODES.indexOf(code) + 1; + } } diff --git a/tilorswift/js/Tilorswift.js b/tilorswift/js/Tilorswift.js index 6ad1e80..4bce0e8 100644 --- a/tilorswift/js/Tilorswift.js +++ b/tilorswift/js/Tilorswift.js @@ -26,6 +26,7 @@ import DialogAddColumns from "./dialog/DialogAddColumns.js"; import Terrain from "./Terrain.js"; import TilorswiftSavedEvent from "./events/TilorswiftSavedEvent.js"; import Level from "../../js/Level.js"; +import {IntelligentBrushSwitch} from "./menu/IntelligentBrushSwitch.js"; export default class Tilorswift { @@ -37,6 +38,8 @@ export default class Tilorswift this.widgetBar = new WidgetBar('widget-bar'); this.tilesetPicker = new TilesetPickerWidget(this.tileset, this.brush); this.widgetBar.addWidget(this.tilesetPicker); + this.intelligentBrushSwitch = new IntelligentBrushSwitch(this.tilesetPicker, this.brush); + this.widgetBar.addWidget(this.intelligentBrushSwitch); this.entrancePicker = new EntrancePointWidget(this.widgetBar, this.brush); this.widgetBar.addWidget(this.entrancePicker); this.targetPicker = new TargetPointWidget(this.widgetBar, this.brush); @@ -124,6 +127,8 @@ export default class Tilorswift this.map.innerHTML = ''; this.map.appendChild(this.level.terrain.getElement()); this.tilesetPicker.reloadTileset(this.tileset); + + this.initializeIntelligentBrushWidget(); } saveLevelToFile() @@ -155,6 +160,63 @@ export default class Tilorswift return true; } + initializeIntelligentBrushWidget() + { + if (this.tileset.hasExtendedTiles()) { + this.intelligentBrushSwitch.enable(); + this.intelligentBrushSwitch.switchOn(); + } else { + this.intelligentBrushSwitch.switchOff(); + this.intelligentBrushSwitch.disable(); + } + } + + createNewTerrain(tilesetIndex, tilesX, tilesY) + { + this.tileset = new Tileset(tilesetIndex); + this.level.terrain = new Terrain(this.tileset, tilesX, tilesY, GraphicSet[tilesetIndex].backgroundColor); + + document.body.style.backgroundColor = this.level.getBackgroundColor(); + + if (GraphicSet[tilesetIndex].backgroundImage !== null) { + document.body.style.backgroundImage = 'url("../' + Setting.GRAPHICS_LOCATION + GraphicSet[tilesetIndex].backgroundImage + '")'; + } else { + document.body.style.backgroundImage = 'none'; + } + + this.map.innerHTML = ''; + this.map.appendChild(this.level.terrain.getElement()); + + this.tilesetPicker.reloadTileset(this.tileset); + + this.initializeIntelligentBrushWidget(); + } + + addTerrain(field) + { + if (this.brush.isIntelligent) { + const index = this.level.terrain.brushTileIndex + this.tileset.primaryTiles * this.tileset.getTileIndexFactor( + this.level.terrain.getFieldNeighbourCode(field) + ); + + field.setIndex(index % this.tileset.tiles); + + for (const neighbour of this.level.terrain.getFieldNeighbours(field)) { + if (neighbour.index === -1) { + continue; + } + + const neighbourIndex = (neighbour.index % this.tileset.primaryTiles) + this.tileset.primaryTiles * this.tileset.getTileIndexFactor( + this.level.terrain.getFieldNeighbourCode(neighbour) + ); + + neighbour.setIndex(neighbourIndex); + } + } else { + field.setIndex(this.level.terrain.brushTileIndex); + } + } + addEventListeners() { window.addEventListener( @@ -163,7 +225,7 @@ export default class Tilorswift if (this.brush.mode === BrushMode.TERRAIN && !event.getField().isEntrancePoint) { switch (event.button) { case 0: - event.getField().setIndex(this.level.terrain.brushTileIndex); + this.addTerrain(event.getField()); break; case 2: event.getField().setIndex(-1); @@ -198,7 +260,7 @@ export default class Tilorswift TilorswiftEvent.FIELD_ENTERED, (event) => { if (this.mouse.isPressedLeft) { - event.getField().setIndex(this.level.terrain.brushTileIndex); + this.addTerrain(event.getField()); } else if (this.mouse.isPressedRight) { event.getField().setIndex(-1); } @@ -257,19 +319,7 @@ export default class Tilorswift window.addEventListener( TilorswiftEvent.NEW_TERRAIN, (event) => { - this.tileset = new Tileset(event.tilesetIndex); - this.level.terrain = new Terrain(this.tileset, event.tilesX, event.tilesY, GraphicSet[event.tilesetIndex].backgroundColor); - - document.body.style.backgroundColor = this.level.getBackgroundColor(); - - if (GraphicSet[event.tilesetIndex].backgroundImage !== null) { - document.body.style.backgroundImage = 'url("../' + Setting.GRAPHICS_LOCATION + GraphicSet[event.tilesetIndex].backgroundImage + '")'; - } else { - document.body.style.backgroundImage = 'none'; - } - this.map.innerHTML = ''; - this.map.appendChild(this.level.terrain.getElement()); - this.tilesetPicker.reloadTileset(this.tileset); + this.createNewTerrain(event.tilesetIndex, event.tilesX, event.tilesY); } ); diff --git a/tilorswift/js/menu/IntelligentBrushSwitch.js b/tilorswift/js/menu/IntelligentBrushSwitch.js new file mode 100644 index 0000000..d3818c1 --- /dev/null +++ b/tilorswift/js/menu/IntelligentBrushSwitch.js @@ -0,0 +1,58 @@ +import Widget from "./Widget.js"; +import {Switch} from "./Switch.js"; + +export class IntelligentBrushSwitch extends Widget +{ + constructor(tilesetPicker, brush) { + super('Intelligenter Pinsel'); + + this.tilesetPicker = tilesetPicker; + this.brush = brush; + + this.switch = new Switch(); + this.switch.onToggle = (status) => { + if (this.isActive) { + this.setIntelligentBrush(status); + } + } + this.htmlElement.appendChild(this.switch.htmlElement); + + if (tilesetPicker.tileset.hasExtendedTiles()) { + this.setIntelligentBrush(true); + } + } + + switchOn() + { + if (!this.switch.status) { + this.switch.toggle() + this.setIntelligentBrush(true); + } + } + + switchOff() + { + if (this.switch.status) { + this.switch.toggle() + this.setIntelligentBrush(false); + } + } + + setIntelligentBrush(status) + { + this.brush.isIntelligent = status; + this.tilesetPicker.updateExtendedTileVisibility(); + } + + enable() { + super.enable(); + + this.switch.enable(); + } + + disable() { + super.disable(); + + this.switch.disable(); + } +} diff --git a/tilorswift/js/menu/Switch.js b/tilorswift/js/menu/Switch.js new file mode 100644 index 0000000..0b4515c --- /dev/null +++ b/tilorswift/js/menu/Switch.js @@ -0,0 +1,50 @@ +export class Switch +{ + constructor(status = false) { + this.htmlElement = document.createElement('div'); + this.htmlElement.classList.add('switch'); + this.slider = document.createElement('div'); + this.slider.classList.add('switch-slider'); + this.htmlElement.appendChild(this.slider); + this.status = status; + this.isEnabled = true; + + this.updateSlider(); + + this.onToggle = () => {} + + this.htmlElement.addEventListener( + 'click', + () => { + if (!this.isEnabled) { + return; + } + + this.toggle(); + this.onToggle(this.status); + } + ) + } + + toggle() + { + this.status = !this.status; + this.updateSlider(); + } + + updateSlider() + { + this.slider.classList.add(this.status ? 'switch-slider-on' : 'switch-slider-off'); + this.slider.classList.remove(this.status ? 'switch-slider-off' : 'switch-slider-on'); + } + + enable() + { + this.isEnabled = true; + } + + disable() + { + this.isEnabled = false; + } +} diff --git a/tilorswift/js/menu/TilesetPickerWidget.js b/tilorswift/js/menu/TilesetPickerWidget.js index b1e919c..df1eeb8 100644 --- a/tilorswift/js/menu/TilesetPickerWidget.js +++ b/tilorswift/js/menu/TilesetPickerWidget.js @@ -24,7 +24,7 @@ export default class TilesetPickerWidget extends Widget this.brush.mode = BrushMode.TERRAIN; } } - ) + ); } loadTileset() @@ -61,7 +61,7 @@ export default class TilesetPickerWidget extends Widget { const htmlElementSelector = document.createElement('div'); htmlElementSelector.id = 'tileset-selector-widget'; - htmlElementSelector.style.width = Math.ceil(Math.sqrt(this.tileset.tiles)) * this.tileset.getTileWidth() + 'px'; + htmlElementSelector.style.width = Math.ceil(Math.sqrt(this.tileset.tiles)) * this.tileset.getTileWidth() + 'px'; htmlElementSelector.style.left = String(this.tileset.getTileWidth() + 1) + 'px'; return htmlElementSelector; @@ -76,4 +76,17 @@ export default class TilesetPickerWidget extends Widget { return this.htmlElement; } + + updateExtendedTileVisibility() + { + const firstExtendedTileIndex = this.tileset.tiles - (this.tileset.tiles - this.tileset.primaryTiles); + + for (const index of this.htmlElementSelector.childNodes.keys()) { + if (index >= firstExtendedTileIndex) { + this.htmlElementSelector.childNodes.item(index).style.display = this.brush.isIntelligent + ? 'none' + : 'inline-flex'; + } + } + } } diff --git a/tilorswift/style.css b/tilorswift/style.css index 4b453b7..7222765 100644 --- a/tilorswift/style.css +++ b/tilorswift/style.css @@ -3,6 +3,16 @@ to { width: 512px; } } +@keyframes switch-toggle-on { + from {left: 0} + to {left: 50%} +} + +@keyframes switch-toggle-off { + from {left: 50%} + to {left: 0} +} + body { padding: 0; margin: 0; @@ -299,8 +309,34 @@ input[type="file"] { padding: 10px; } -/* -tr:hover > td > .selection { - opacity: 0.5; +.switch { + position: relative; + border-radius: 16px; + border: 2px solid #333333; + width: 100%; + height: 32px; + cursor: pointer; + background-color: #777777; +} + +.switch-slider { + position: absolute; + border-radius: 14px; + width: 50%; + height: 100%; + border: 1px solid white; +} + +.switch-slider-on { + left: 50%; + background-color: #2222aa; + animation-name: switch-toggle-on; + animation-duration: 0.2s; +} + +.switch-slider-off { + left: 0; + background-color: #888888; + animation-name: switch-toggle-off; + animation-duration: 0.2s; } -*/