Browse Source

Mobile client files

pull/14/head
Armen138 9 months ago
parent
commit
e6b7fecda4
25 changed files with 949 additions and 8 deletions
  1. +3
    -3
      game/character.js
  2. +34
    -0
      game/dummyrenderer.js
  3. +51
    -2
      game/game.js
  4. +3
    -0
      game/menu.js
  5. +1
    -0
      game/monster.js
  6. +0
    -2
      game/webloader.js
  7. +1
    -1
      game/world.js
  8. +70
    -0
      mobile.html
  9. +61
    -0
      mobile.js
  10. +94
    -0
      mobile/components/equipment.js
  11. +114
    -0
      mobile/components/go.js
  12. +51
    -0
      mobile/components/history.js
  13. +149
    -0
      mobile/components/inventory.js
  14. +122
    -0
      mobile/components/items.js
  15. +51
    -0
      mobile/components/location.js
  16. +88
    -0
      mobile/components/monsters.js
  17. +16
    -0
      mobile/components/profile.js
  18. +40
    -0
      mobile/components/stats.js
  19. BIN
      mobile/images/items/backpack.png
  20. BIN
      mobile/images/items/baseball_bat.png
  21. BIN
      mobile/images/items/baseball_cap.png
  22. BIN
      mobile/images/items/orange.png
  23. BIN
      mobile/images/items/rusty_nail.png
  24. BIN
      mobile/images/strange_room.jpg
  25. BIN
      mobile/images/wall.jpg

+ 3
- 3
game/character.js View File

@@ -1,9 +1,9 @@
/* eslint-disable arrow-parens */
/* eslint-disable import/extensions */
/* eslint-disable no-restricted-syntax */
import Messages from './messages.js';
// import Messages from './messages.js';

const messages = new Messages('data/messages.yml');
// const messages = new Messages('data/messages.yml');
class Character {
constructor() {
this.inventory = [];
@@ -57,7 +57,7 @@ class Character {
resolve(`You have added ${item.name} to your inventory.`);
}
} else {
reject(messages.inventory_full);
reject(this.messages ? this.messages.inventory_full : 'inventory full');
}
});
return promise;


+ 34
- 0
game/dummyrenderer.js View File

@@ -0,0 +1,34 @@
/* eslint-disable no-restricted-syntax */
/* eslint-disable class-methods-use-this */

class DummyRenderer {
show() {}

prompt() {}

clear() {}

register() {}

style(text, style) {
if (!text) return '';
let elementType = 'span';
if (style && style['text-align']) {
elementType = 'div';
}
const span = document.createElement(elementType);
let styleAttribute = '';
for (const item in style) {
if (Object.prototype.hasOwnProperty.call(style, item)) {
styleAttribute += `${item}:${style[item]};`;
}
}
span.setAttribute('style', styleAttribute);
span.innerHTML = text.replace(/\n/g, '<br>');
return span.outerHTML;
}

text() {}
}

export default DummyRenderer;

+ 51
- 2
game/game.js View File

@@ -9,10 +9,10 @@ import Events from './events.js';
// import Monsters from './monsters.js';

const healthScale = [
'red',
'#EF5350',
'orange',
'yellow',
'green',
'#CDDC39',
];

class Game extends Events {
@@ -25,12 +25,15 @@ class Game extends Events {
loader.get('data/world.yml').then(worldConfig => {
loader.get(`data/locations/${worldConfig.spawn.replace(/ /g, '_')}.yml`).then(location => {
this.world = new World(location, loader);
this.world.items = this.items;
this.character = new Character();
loader.get('data/messages.yml').then(messageData => {
this.messages = new Messages(messageData);
this.character.messages = this.messages;
loader.get('data/countdown.yml').then(countdown => {
this.countdown = countdown;
this.emit('ready');
this.emit('location', this.world.location);
});
});
});
@@ -56,6 +59,13 @@ class Game extends Events {
this.renderer.register(this.commands, this.autocomplete.bind(this));
}

get itemsHere() {
if (this.world && this.world.location && this.world.location.items) {
return this.world.location.items.length > 0;
}
return false;
}

autocomplete(command) {
switch (command) {
case 'take':
@@ -105,7 +115,10 @@ class Game extends Events {
}
if (worldItem.effect.prints) {
this.renderer.text(worldItem.effect.prints);
this.emit('message', worldItem.effect.prints);
}
this.emit('location-items', this.world.location.items);
this.emit('inventory', this.character.inventory);
}

attack(monster, callback) {
@@ -113,21 +126,28 @@ class Game extends Events {
if (monsterIdx !== -1) {
const damage = this.character.attack();
this.renderer.text(damage.message);
this.emit('message', damage.message);
const status = this.world.location.spawned[monsterIdx].defend(damage);
this.renderer.text(status.message, null, monster);
this.emit('message-add', status.message);
if (status.status === 'death') {
this.world.location.spawned.splice(monsterIdx, 1);
if (status.drops && status.drops.length > 0) {
this.world.location.items = this.world.location.items.concat(status.drops);
for (const drop of status.drops) {
this.renderer.text(`It dropped ${this.items.render(drop)}`, null, monster);
this.emit('message-add', `It dropped ${this.items.render(drop)}`);
this.emit('location-items', this.world.location.items);
}
}
}
this.advance();
} else {
this.renderer.text(this.messages.not_found, { color: 'red' });
this.emit('error', this.messages.not_found);
}
this.emit('monsters', this.world.location.spawned);
this.emit('stats', this.character);
callback();
}

@@ -136,6 +156,7 @@ class Game extends Events {
const inventorySlot = this.character.inventory.indexOf(itemName);
if (inventorySlot === -1) {
this.renderer.text(this.messages.not_in_inventory, { color: 'red' });
this.emit('error', this.messages.not_in_inventory);
used = true;
} else {
const inventoryItem = this.items.get(itemName);
@@ -143,18 +164,23 @@ class Game extends Events {
if (this.character.equipment[equipmentSlot] === null) {
this.character.equipment[equipmentSlot] = inventoryItem;
this.character.inventory.splice(inventorySlot, 1);
this.emit('message', `You've equipped ${itemName} in the ${equipmentSlot} slot`);
this.emit('inventory', this.character.inventory);
this.renderer.text(`You've equipped ${itemName} in the ${equipmentSlot} slot`);
used = true;
} else {
this.renderer.text(this.messages.slot_used, { color: 'red' });
this.emit('error', this.messages.slot_used);
used = true;
}
}
if (!used) {
this.renderer.text(this.messages.cant_use, { color: 'red' });
this.emit('error', this.messages.cant_use);
} else {
this.advance();
}
this.emit('stats', this.character);
callback();
}

@@ -166,9 +192,13 @@ class Game extends Events {
if (this.character.inventory.length < this.character.inventorySlots) {
this.character.inventory.push(itemName);
this.renderer.text(`You've unequipped ${itemName}.`);
this.emit('message', `You've unequipped ${itemName}.`);
this.emit('inventory', this.character.inventory);
} else {
this.world.location.items.push(itemName);
this.renderer.text(`You've unequipped ${itemName}, and dropped it on the floor in front of you.`);
this.emit('message', `You've unequipped ${itemName}, and dropped it on the floor in front of you.`);
this.emit('location-items', this.world.location.items);
}
used = true;
break;
@@ -179,6 +209,7 @@ class Game extends Events {
} else {
this.advance();
}
this.emit('stats', this.character);
callback();
}

@@ -187,6 +218,7 @@ class Game extends Events {
const inventorySlot = this.character.inventory.indexOf(itemName);
if (inventorySlot === -1) {
this.renderer.text(this.messages.not_in_inventory, { color: 'red' });
this.emit('error', this.messages.not_in_inventory);
used = true;
} else {
const inventoryItem = this.items.get(itemName);
@@ -206,6 +238,7 @@ class Game extends Events {
}
if (!used) {
this.renderer.text(this.messages.cant_use, { color: 'red' });
this.emit('error', this.messages.cant_use);
} else {
this.advance();
}
@@ -217,6 +250,7 @@ class Game extends Events {
this.renderer.clear();
this.prompt();
this.look(null, callback);
this.emit('location', this.world.location);
});
}

@@ -224,10 +258,12 @@ class Game extends Events {
const inventorySlot = this.character.inventory.indexOf(itemName);
if (inventorySlot === -1) {
this.renderer.text(this.messages.not_in_inventory, { color: 'red' });
this.emit('error', this.messages.not_in_inventory);
} else {
const item = this.items.get(itemName);
if (!item.health) {
this.renderer.text(this.messages.not_edible, { color: 'red' });
this.emit('error', this.messages.not_edible);
} else {
this.character.health += item.health;
if (this.character.health > this.character.maxHealth) {
@@ -237,7 +273,9 @@ class Game extends Events {
const color = Math.floor((healthScale.length / this.character.maxHealth)
* this.character.health) - 1;
const health = this.renderer.style(`${this.character.health}/${this.character.maxHealth}`, { color: healthScale[color] });
this.emit('message', `You ate the ${this.items.render(itemName)}. Your health is now ${health}`);
this.renderer.text(`You ate the ${this.items.render(itemName)}. Your health is now ${health}`);
this.emit('inventory', this.character.inventory);
this.advance();
}
}
@@ -248,9 +286,13 @@ class Game extends Events {
const item = this.items.get(itemName);
this.character.take(this.world, item).then(message => {
this.renderer.text(message);
this.emit('location-items', this.world.location.items);
this.emit('inventory', this.character.inventory);
this.emit('message', message);
this.advance();
callback();
}).catch(error => {
this.emit('error', error);
this.renderer.text(error, { color: 'red' });
callback();
});
@@ -268,13 +310,17 @@ class Game extends Events {
drop(itemName, callback) {
const inventorySlot = this.character.inventory.indexOf(itemName);
if (inventorySlot === -1) {
this.emit('error', this.messages.not_in_inventory);
this.renderer.text(this.messages.not_in_inventory, { color: 'red' });
} else {
this.world.location.items.push(itemName);
this.character.inventory.splice(inventorySlot, 1);
this.emit('message', `You drop the ${itemName}.`);
this.renderer.text(`You drop the ${itemName}.`);
this.advance();
}
this.emit('location-items', this.world.location.items);
this.emit('inventory', this.character.inventory);
callback();
}

@@ -336,8 +382,10 @@ class Game extends Events {
for (const monster of monstersHere) {
const damage = monster.attack();
this.renderer.text(damage.message, null, monster.name);
this.emit('message-add', damage.message);
const status = this.character.defend(damage);
this.renderer.text(status.message, null, 'you');
this.emit('message-add', status.message);
if (status.status === 'death') {
this.emit('death');
}
@@ -347,6 +395,7 @@ class Game extends Events {
const time = `[${this.countdown.length} minutes to midnight] `;
const message = this.countdown.shift();
this.renderer.text(time + message, { color: 'grey' });
this.emit('message-add', time + message);
} else {
this.emit('death'); // maybe this should be another event
}


+ 3
- 0
game/menu.js View File

@@ -39,6 +39,9 @@ class Menu extends Events {
this.renderer.text(this.help());
this.renderer.prompt(this.prompt);
this.renderer.show();

// for fancy front-ends
this.emit('menu', { data: this.data, commands: this.commands });
}

help(cmd) {


+ 1
- 0
game/monster.js View File

@@ -31,6 +31,7 @@ class Monster {
// - thigh bone
// - leather boots
this.items = items;
this.description = config.description || '';
this.name = config.name || 'Monster';
this.health = config.health || 1;
this.damage = config.damage || 1;


+ 0
- 2
game/webloader.js View File

@@ -1,7 +1,5 @@
/* eslint-disable import/extensions */
/* eslint-disable arrow-parens */
// import yaml from 'js-yaml';
// import * as yaml from '../node_modules/js-yaml/dist/js-yaml.js';

class WebLoader {
constructor() {


+ 1
- 1
game/world.js View File

@@ -42,7 +42,7 @@ class World {
try {
this.loader.get(`data/locations/${location.replace(/ /g, '_')}.yml`).then(data => {
if (data.monsters) {
data.spawned = data.monsters.map((monster) => new Monster(this.monsters.get(monster)));
data.spawned = data.monsters.map((monster) => new Monster(this.monsters.get(monster), this.items));
}
this.location = data;
resolve(data);


+ 70
- 0
mobile.html View File

@@ -0,0 +1,70 @@
<!DOCTYPE html>
<html>

<head>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@3.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
</head>

<body>
<div id="app">
<v-app>
<profile></profile>

<v-content style="margin-bottom: 56px" dark>
<v-container v-show="view=='look'">
<stats :game="game"></stats>
<location :game="game"></location>
<items :game="game"></items>
<monsters :game="game"></monsters>
<go :game="game"></go>
</v-container>
<v-container v-show="view=='inventory'">
<inventory :game="game"></inventory>
</v-container>
<v-container v-show="view=='equipment'">
<equipment :game="game"></equipment>
</v-container>
<v-container v-show="view=='history'">
<history :game="game"></history>
</v-container>
</v-content>
<v-snackbar top v-model="snackbar" :color="notificationColor">
<span v-html="notification"></span>
<v-btn
text
multi-line
@click="snackbar = false"
>
Close
</v-btn>
</v-snackbar>
<v-bottom-navigation v-model="view" fixed grow dark color="#f4511e">
<v-btn active value="look">
<span>Look</span>
<v-icon>mdi-eye</v-icon>
</v-btn>
<v-btn value="inventory">
<span>Inventory</span>
<v-icon>mdi-toolbox</v-icon>
</v-btn>
<v-btn value="equipment">
<span>Equipment</span>
<v-icon>mdi-sword</v-icon>
</v-btn>
<v-btn value="history">
<span>History</span>
<v-icon>mdi-history</v-icon>
</v-btn>
</v-bottom-navigation>
</v-app>
</div>
<script src='node_modules/js-yaml/dist/js-yaml.min.js'></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@2.x/dist/vuetify.js"></script>
<script src="mobile.js" type="module"></script>
</body>

</html>

+ 61
- 0
mobile.js View File

@@ -0,0 +1,61 @@
/* eslint-disable no-unused-vars */
/* eslint-disable import/extensions */
/* eslint-disable no-undef */
import location from './mobile/components/location.js';
import inventory from './mobile/components/inventory.js';
import items from './mobile/components/items.js';
import profile from './mobile/components/profile.js';
import monsters from './mobile/components/monsters.js';
import stats from './mobile/components/stats.js';
import go from './mobile/components/go.js';
import equipment from './mobile/components/equipment.js';
import history from './mobile/components/history.js';

import Game from './game/game.js';
import Menu from './game/menu.js';
// import WebRenderer from './game/webrenderer.js';
import DummyRenderer from './game/dummyrenderer.js';
import WebLoader from './game/webloader.js';

const loader = new WebLoader();
const game = new Game(new DummyRenderer(), loader);

Vue.component(location.name, location);
Vue.component(inventory.name, inventory);
Vue.component(items.name, items);
Vue.component(profile.name, profile);
Vue.component(monsters.name, monsters);
Vue.component(stats.name, stats);
Vue.component(go.name, go);
Vue.component(equipment.name, equipment);
Vue.component(history.name, history);

const vue = new Vue({
el: '#app',
created() {
game.on('message', (message) => {
this.notification = message;
this.snackbar = true;
this.notificationColor = 'primary';
});
game.on('message-add', (message) => {
this.notification = [this.notification, message].join('<br>');
this.snackbar = true;
this.notificationColor = 'primary';
});
game.on('error', (message) => {
this.notification = message;
this.notificationColor = 'error';
this.snackbar = true;
});
},
data: () => ({
game,
notification: '',
notificationColor: 'primary',
snackbar: false,
view: 'look',
inventory: false,
}),
vuetify: new Vuetify(),
});

+ 94
- 0
mobile/components/equipment.js View File

@@ -0,0 +1,94 @@
/* eslint-disable guard-for-in */
/* eslint-disable no-restricted-syntax */
const template = /* html */`
<div>
<v-card
class="mx-auto ma-2 pb-2"
dark
max-width="640"
>
<v-card-title>
<v-icon
large
left
>
mdi-sword
</v-icon>
<span class="title font-weight-light">Equipment</span>
</v-card-title>
</v-card>
<v-card
class="mx-auto mb-2"
max-width="640"
v-for="(item, index) in items"
dark
outlined
>
<v-list-item three-line>
<v-list-item-content>
<div class="overline mb-4">{{ item.prevalence }}</div>
<v-list-item-subtitle>{{ item.slot }}</v-list-item-subtitle>
<v-list-item-title class="headline mb-1" v-html="game.items.render(item.name)"></v-list-item-title>
<v-list-item-subtitle>{{ item.description }}</v-list-item-subtitle>
</v-list-item-content>

<v-list-item-avatar
tile
size="80"
color="grey"
>
<v-img :src="itemImage(item)"></v-img>
</v-list-item-avatar>
</v-list-item>

<v-card-actions>
<v-btn small text @click='unequip(item)'>unequip</v-btn>
</v-card-actions>
</v-card>

</div>
`;
const equipment = {
name: 'equipment',
props: ['game'],
created() {
this.game.on('stats', (data) => {
this.items = [];
for (const item in data.equipment) {
if (data.equipment[item]) {
const itemCopy = {};
console.log(data.equipment[item]);
itemCopy.description = data.equipment[item].description;
itemCopy.prevalence = data.equipment[item].prevalence;
itemCopy.name = data.equipment[item].name;
itemCopy.damage = data.equipment[item].damage;
itemCopy.armor = data.equipment[item].armor;
itemCopy.slot = item;
this.items.push(itemCopy);
}
}
});
// this.game.on('ready', () => {
// this.items = this.game.character.inventory.map((item) => this.game.items.get(item));
// });
},
methods: {
unequip(item) {
this.game.unequip(item.name, () => { });
},
itemImage(item) {
return `mobile/images/items/${item.name.replace(/ /g, '_')}.png`;
},
},
data: () => ({
snackbar: false,
dialog: false,
max: 2,
used: 0,
selected: {},
items: [],
}),
template,
};

export default equipment;

+ 114
- 0
mobile/components/go.js View File

@@ -0,0 +1,114 @@
const template = /* html */`
<v-card
class="mx-auto ma-2"
dark
max-width="640"
v-if="items.length > 0"
>
<v-card-title>
<v-icon
large
left
>
mdi-door
</v-icon>
<span class="title font-weight-light">Go to</span>
</v-card-title>
<v-card-text>
<v-list two-line>

<template v-for="(item, index) in items">
<v-list-item :key="item.title">
<template v-slot:default="{ active, toggle }">
<v-list-item-content>
<v-list-item-title >{{ item }}</v-list-item-title>
</v-list-item-content>

<v-list-item-action>
<v-btn icon @click="go(item)">
<v-icon color="grey lighten-1">
mdi-logout
</v-icon>
</v-btn>
</v-list-item-action>
</template>
</v-list-item>

<v-divider
v-if="index + 1 < items.length"
:key="index"
></v-divider>
</template>

</v-list>
</v-card-text>
<v-snackbar
v-model="snackbar"
><span>
You've added <span style="color: yellow">{{ selected.name }}</span> to your inventory.
</span>
<v-btn
color="pink"
text
multi-line
@click="snackbar = false"
>
Close
</v-btn>
</v-snackbar>
<v-dialog
v-model="dialog"
max-width="290"
dark
>
<v-card>
<v-card-title class="headline">{{ selected.name }}</v-card-title>

<v-card-text>
{{ selected.description }}
</v-card-text>

<v-card-actions>
<v-spacer></v-spacer>

<v-btn
color="green darken-1"
text
@click="dialog = false"
>
I see.
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-card>
`;
const go = {
name: 'go',
props: ['game'],
created() {
this.game.on('location', (data) => {
this.items = data.connects;
});
this.game.on('connections', (data) => {
this.items = data;
});
this.game.on('ready', () => {
this.items = this.game.world.location.connects;
});
},
methods: {
go(item) {
this.game.go(item, () => {});
},
},
data: () => ({
snackbar: false,
dialog: false,
selected: {},
items: [],
}),
template,
};

export default go;

+ 51
- 0
mobile/components/history.js View File

@@ -0,0 +1,51 @@
/* eslint-disable guard-for-in */
/* eslint-disable no-restricted-syntax */
const template = /* html */`
<div>
<v-card
class="mx-auto ma-2 pb-2"
dark
max-width="640"
>
<v-card-title>
<v-icon
large
left
>
mdi-history
</v-icon>
<span class="title font-weight-light">History</span>
</v-card-title>
</v-card>
<v-alert v-for="item in items" :type="item.type" v-html="item.text"></v-alert>
</div>
`;
const history = {
name: 'history',
props: ['game'],
created() {
this.game.on('message', (data) => {
this.items.unshift({ text: data, type: 'primary' });
});
this.game.on('message-add', (data) => {
this.items.unshift({ text: data, type: 'primary' });
});
this.game.on('error', (data) => {
this.items.unshift({ text: data, type: 'error' });
});
},
methods: {
unequip(item) {
this.game.unequip(item.name, () => { });
},
itemImage(item) {
return `mobile/images/items/${item.name.replace(/ /g, '_')}.png`;
},
},
data: () => ({
items: [],
}),
template,
};

export default history;

+ 149
- 0
mobile/components/inventory.js View File

@@ -0,0 +1,149 @@
/* eslint-disable no-restricted-syntax */
const template = /* html */`
<div>
<v-card
class="mx-auto ma-2 pb-2"
dark
max-width="640"
>
<v-card-title>
<v-icon
large
left
>
mdi-bag-personal
</v-icon>
<span class="title font-weight-light">Inventory</span>
<v-spacer></v-spacer>{{ used }}/{{ max }}
</v-card-title>
</v-card>
<v-card
class="mx-auto mb-2"
max-width="640"
v-for="(item, index) in items"
dark
outlined
>
<v-list-item three-line>
<v-list-item-content>
<div class="overline mb-4">{{ item.prevalence }}</div>
<v-list-item-title class="headline mb-1" v-html="game.items.render(item.name)"></v-list-item-title>
<v-list-item-subtitle>{{ item.description }}</v-list-item-subtitle>
</v-list-item-content>

<v-list-item-avatar
tile
size="80"
color="grey"
>
<v-img :src="itemImage(item)"></v-img>
</v-list-item-avatar>
</v-list-item>

<v-card-actions>
<v-btn small text @click='use(item)' v-show="item.consumable || isUsable(item)">use</v-btn>
<v-btn small text @click='eat(item)' v-show="item.health">eat</v-btn>
<v-btn small text @click='equip(item)' v-show="item.equip">equip</v-btn>
<v-btn small text @click='drop(item)'>drop</v-btn>
</v-card-actions>
</v-card>

<v-snackbar
v-model="snackbar"
><span>
You've added <span style="color: yellow">{{ selected.name }}</span> to your inventory.
</span>
<v-btn
color="pink"
text
multi-line
@click="snackbar = false"
>
Close
</v-btn>
</v-snackbar>
<v-dialog
v-model="dialog"
max-width="290"
dark
>
<v-card>
<v-card-title class="headline">{{ selected.name }}</v-card-title>

<v-card-text>
{{ selected.description }}
</v-card-text>

<v-card-actions>
<v-spacer></v-spacer>

<v-btn
color="green darken-1"
text
@click="dialog = false"
>
I see.
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
`;
const inventory = {
name: 'inventory',
props: ['game'],
created() {
this.game.on('location', () => {
this.$forceUpdate();
});
this.game.on('inventory', (data) => {
this.items = data.map((item) => this.game.items.get(item));
this.max = this.game.character.inventorySlots;
this.used = data.length;
});
this.game.on('ready', () => {
this.items = this.game.character.inventory.map((item) => this.game.items.get(item));
});
},
methods: {
use(item) {
this.game.use(item.name, () => {});
},
eat(item) {
this.game.eat(item.name, () => {});
},
equip(item) {
this.game.equip(item.name, () => {});
},
drop(item) {
this.game.drop(item.name, () => {});
},
examine(item) {
this.game.examine(item.name, () => {});
},
itemImage(item) {
return `mobile/images/items/${item.name.replace(/ /g, '_')}.png`;
},
isUsable(item) {
if (this.game.world) {
for (const worldItem of this.game.world.location.items) {
if (this.game.items.get(worldItem)['reacts with'] === item.name) {
return true;
}
}
}
return false;
},
},
data: () => ({
snackbar: false,
dialog: false,
max: 2,
used: 0,
selected: {},
items: [],
}),
template,
};

export default inventory;

+ 122
- 0
mobile/components/items.js View File

@@ -0,0 +1,122 @@
const template = /* html */`
<v-card
class="mx-auto ma-2"
dark
max-width="640"
v-if="items.length > 0"
>
<v-card-title>
<v-icon
large
left
>
mdi-bag-personal
</v-icon>
<span class="title font-weight-light">Items here</span>
</v-card-title>
<v-card-text>
<v-list two-line>

<template v-for="(item, index) in items">
<v-list-item :key="item.title">
<template v-slot:default="{ active, toggle }">
<v-list-item-content>
<v-list-item-title v-html="game.items.render(item.name)"></v-list-item-title>
<v-list-item-subtitle v-text="item.prevalence"></v-list-item-subtitle>
<v-list-item-subtitle v-text="item.description"></v-list-item-subtitle>
</v-list-item-content>

<v-list-item-action>
<v-btn icon @click="dialog = true; selected=item">
<v-icon color="grey lighten-1">
mdi-eye
</v-icon>
</v-btn>

<v-btn icon @click="take(item)" v-show="item.prevalence !== 'static'">
<v-icon color="grey lighten-1">
mdi-hand
</v-icon>
</v-btn>
</v-list-item-action>
</template>
</v-list-item>

<v-divider
v-if="index + 1 < items.length"
:key="index"
></v-divider>
</template>

</v-list>
</v-card-text>
<v-snackbar
v-model="snackbar"
><span>
You've added <span style="color: yellow">{{ selected.name }}</span> to your inventory.
</span>
<v-btn
color="pink"
text
multi-line
@click="snackbar = false"
>
Close
</v-btn>
</v-snackbar>
<v-dialog
v-model="dialog"
max-width="290"
dark
>
<v-card>
<v-card-title class="headline">{{ selected.name }}</v-card-title>

<v-card-text>
{{ selected.description }}
</v-card-text>

<v-card-actions>
<v-spacer></v-spacer>

<v-btn
color="green darken-1"
text
@click="dialog = false"
>
I see.
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-card>
`;
const items = {
name: 'items',
props: ['game'],
created() {
this.game.on('location', (data) => {
this.items = data.items.map((item) => this.game.items.get(item));
});
this.game.on('location-items', (data) => {
this.items = data.map((item) => this.game.items.get(item));
});
this.game.on('ready', () => {
this.items = this.game.world.location.items.map((item) => this.game.items.get(item));
});
},
methods: {
take(item) {
this.game.take(item.name, () => {});
},
},
data: () => ({
snackbar: false,
dialog: false,
selected: {},
items: [],
}),
template,
};

export default items;

+ 51
- 0
mobile/components/location.js View File

@@ -0,0 +1,51 @@
const template = `
<v-card
class="mx-auto"
dark
max-width="640"
>
<v-img
src="mobile/images/strange_room.jpg"
class="white--text"
height="200px"
gradient="to bottom, rgba(0,0,0,.1), rgba(0,0,0,.5)"
>
<v-card-title
class="fill-height align-end"
>
<v-icon
large
left
>
mdi-eye
</v-icon>
<span class="title font-weight-light" style="text-transform: capitalize">{{ location.name }}</span>

</v-card-title>
</v-img>
<v-card-text class="headline">
{{ location.title }}
</v-card-text>
<v-card-text>
{{ location.description }}
</v-card-text>
</v-card>
`;
const location = {
name: 'location',
props: ['game'],
created() {
this.game.on('location', (data) => {
this.location = data;
});
this.game.on('ready', () => {
this.location = this.game.world.location;
});
},
data: () => ({
location: {},
}),
template,
};

export default location;

+ 88
- 0
mobile/components/monsters.js View File

@@ -0,0 +1,88 @@
const template = /* html */`
<v-card
class="mx-auto ma-2"
dark
max-width="640"
v-show="items.length > 0"
>
<v-card-title>
<v-icon
large
left
>
mdi-emoticon-devil
</v-icon>
<span class="title font-weight-light">Monsters here</span>
</v-card-title>
<v-card-text>
<v-list two-line>

<template v-for="(item, index) in items">
<v-list-item :key="item.name">
<template v-slot:default="{ active, toggle }">
<v-list-item-content>
<v-list-item-title v-text="item.name"></v-list-item-title>
<v-list-item-subtitle v-text="item.description"></v-list-item-subtitle>
<v-progress-linear rounded color="red" :value="item.maxHealth / item.health * 100" height="10"></v-progress-linear>
</v-list-item-content>
<v-list-item-action>
<v-btn icon @click="attack(item)">
<v-icon color="grey lighten-1">
mdi-sword
</v-icon>
</v-btn>
</v-list-item-action>
</template>
</v-list-item>

<v-divider
v-if="index + 1 < items.length"
:key="index"
></v-divider>
</template>

</v-list>
</v-card-text>
<v-snackbar
v-model="snackbar"
><span>
You've added <span style="color: yellow">item with super long name</span> to your inventory.
</span>
<v-btn
color="pink"
text
multi-line
@click="snackbar = false"
>
Close
</v-btn>
</v-snackbar>
</v-card>
`;
const monsters = {
name: 'monsters',
props: ['game'],
created() {
this.game.on('location', (location) => {
this.items = location.spawned || [];
});
this.game.on('ready', () => {
this.items = this.game.world.location.spawned || [];
});
this.game.on('monsters', (spawned) => {
this.items = spawned || [];
});
},
methods: {
attack(item) {
this.game.attack(item.name, () => {});
},
},
data: () => ({
snackbar: false,
items: [],
}),
template,
};

export default monsters;

+ 16
- 0
mobile/components/profile.js View File

@@ -0,0 +1,16 @@
const template = `<v-app-bar app dark class="mb-2" color="#BF360C" fixed>
<v-app-bar-nav-icon></v-app-bar-nav-icon>
<v-toolbar-title>Night Terror</v-toolbar-title>
</v-app-bar>`;
const profile = {
name: 'profile',
data: () => ({
snackbar: false,
health: 10,
armor: 4,
damage: 1,
}),
template,
};

export default profile;

+ 40
- 0
mobile/components/stats.js View File

@@ -0,0 +1,40 @@
const template = `<v-card
class="mx-auto mb-2" max-width="640" color="#5d4037" dark>
<v-list-item>
<v-icon color="red">mdi-heart</v-icon>&nbsp;
<v-progress-linear rounded color="red" :value="health * 10" height="20"></v-progress-linear>
</v-list-item>
<v-list-item>
<v-icon color="yellow">mdi-shield</v-icon>&nbsp;
<v-progress-linear rounded color="yellow" :value="armor * 10" height="20"></v-progress-linear>
</v-list-item>
<v-list-item>
<v-icon color="green">mdi-sword</v-icon>&nbsp;
<v-progress-linear rounded color="green" :value="damage * 10" height="20"></v-progress-linear>
</v-list-item>
</v-card>
`;
const stats = {
name: 'stats',
props: ['game'],
created() {
this.game.on('ready', () => {
this.health = this.game.character.health;
this.armor = this.game.character.armor;
this.damage = this.game.character.damage;
});
this.game.on('stats', () => {
this.health = this.game.character.health;
this.armor = this.game.character.armor;
this.damage = this.game.character.damage;
});
},
data: () => ({
health: 0,
armor: 0,
damage: 0,
}),
template,
};

export default stats;

BIN
mobile/images/items/backpack.png View File

Before After
Width: 512  |  Height: 512  |  Size: 15KB

BIN
mobile/images/items/baseball_bat.png View File

Before After
Width: 512  |  Height: 512  |  Size: 21KB

BIN
mobile/images/items/baseball_cap.png View File

Before After
Width: 512  |  Height: 512  |  Size: 14KB

BIN
mobile/images/items/orange.png View File

Before After
Width: 512  |  Height: 512  |  Size: 17KB

BIN
mobile/images/items/rusty_nail.png View File

Before After
Width: 512  |  Height: 512  |  Size: 12KB

BIN
mobile/images/strange_room.jpg View File

Before After
Width: 710  |  Height: 473  |  Size: 47KB

BIN
mobile/images/wall.jpg View File

Before After
Width: 396  |  Height: 608  |  Size: 85KB

Loading…
Cancel
Save