All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+### [0.2.10](https://git.xangelo.ca/?p=risinglegends.git;a=commitdiff;h=v0.2.10;hp=v0.2.9;ds=sidebyside) (2023-08-18)
+
+
+### Features
+
+* add icons for beginner equipment 218a9ee
+* increase hp gain rate 1f9aaf6
+
+
+### Bug Fixes
+
+* background not appearing if reload during fight 64a76af
+* cant perform other actions in a fight b1a1999
+* chat timeline to messages show up chronologically b82a2ef
+* dont display death text after fleeing 2a1bffe
+* missing % from player bar a0606a5
+* move purchase button under icon in stores 93aeef5
+* only disable equipping/unequipping in a fight b6e9f9a
+
### [0.2.9](https://git.xangelo.ca/?p=risinglegends.git;a=commitdiff;h=v0.2.9;hp=v0.2.8;ds=sidebyside) (2023-08-15)
{
"name": "rising-legends",
- "version": "0.2.9",
+ "version": "0.2.10",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "rising-legends",
- "version": "0.2.9",
+ "version": "0.2.10",
"dependencies": {
"@honeycombio/opentelemetry-node": "^0.4.0",
"@opentelemetry/auto-instrumentations-node": "^0.37.0",
{
"name": "rising-legends",
"private": true,
- "version": "0.2.9",
+ "version": "0.2.10",
"scripts": {
"up": "npx prisma migrate dev --name \"init\"",
"start": "pm2 start dist/server/api.js",
.store-list {
display: flex;
text-align: left;
- margin: 0 0 0.3rem 0.3rem;
-}
-.store-list:last-child {
- margin-bottom: 0;
+ padding: 0.5rem;
}
-.store-list img {
- width: 64px;
- height: 64px;
+.store-list:nth-child(even) {
+ background-color: #f2f0ec;
}
.store-list .details {
padding: 0 0.4rem;
.store-list .name {
font-weight: bold;
}
+.requirements {
+ margin-top: 0.5rem;
+ line-height: 1.3rem;
+}
.requirement-title {
font-weight: bold;
text-transform: capitalize;
}
+.store-cost {
+ margin-top: 0.5rem;
+}
+.store-icon {
+ width: 64px;
+ height: calc(64px + 27px);
+ padding: 0;
+ background-repeat: no-repeat;
+ background-size: contain;
+ position: relative;
+ margin-right: 0.5rem;
+}
+.inventory-icon {
+ width: 64px;
+ height: 64px;;
+ padding: 0;
+ background-repeat: no-repeat;
+ background-size: contain;
+ position: relative;
+ margin-right: 0.5rem;
+}
.store-actions {
- width: 75px;
- margin: 0.3rem;
- align-items: center;
- display: flex;
- flex-direction: column;
+ width: 100%;
+ position: absolute;
+ bottom: 0;
}
.store-actions button {
- width: 75px;
+ width: 100%;
+ padding: 0.3rem 0.5rem;
+}
+.inventory-actions {
+ width: 74px;
+}
+.inventory-actions button {
+ width: 100%;
padding: 0.3rem 0.5rem;
}
</div>
<section id="chat">
- <div id="chat-messages" hx-trigger="load delay:1s" hx-get="/chat/history" hx-swap="afterend"></div>
+ <div id="chat-messages" hx-trigger="load delay:1s" hx-get="/chat/history" hx-swap="innerHTML"></div>
<form id="chat-form" hx-post="/chat">
<input type="text" id="message" name="message" autocomplete="off"><button type="submit">Send</button>
</form>
import {FightRound} from '../shared/fight';
import {addInventoryItem, deleteInventoryItem, getEquippedItems, getInventory, getInventoryItem, updateAp} from './inventory';
import { getItemFromPlayer, getItemFromShop, getPlayersItems, getShopItems, givePlayerItem, updateItemCount } from './items';
-import {FightTrigger, Monster, MonsterForFight, MonsterWithFaction} from '../shared/monsters';
+import {Fight, FightTrigger, Monster, MonsterForFight, MonsterWithFaction} from '../shared/monsters';
import {getShopEquipment, listShopItems } from './shopEquipment';
import {EquippedItemDetails} from '../shared/equipped';
import {ArmourEquipmentSlot, EquipmentSlot} from '../shared/inventory';
res.send(renderInventoryPage(inventory, items));
});
-app.post('/player/equip/:item_id/:slot', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/player/equip/:item_id/:slot', authEndpoint, blockPlayerInFight, async (req: AuthRequest, res: Response) => {
const inventoryItem = await getInventoryItem(req.player.id, req.params.item_id);
const equippedItems = await getEquippedItems(req.player.id);
const requestedSlot = req.params.slot;
res.send(renderInventoryPage(inventory, items, inventoryItem.type) + renderPlayerBar(req.player, inventory));
});
-app.post('/player/unequip/:item_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/player/unequip/:item_id', authEndpoint, blockPlayerInFight, async (req: AuthRequest, res: Response) => {
const [item, ] = await Promise.all([
getInventoryItem(req.player.id, req.params.item_id),
unequip(req.player.id, req.params.item_id)
res.send(renderInventoryPage(inventory, items, item.type) + renderPlayerBar(req.player, inventory));
});
+async function blockPlayerInFight(req: AuthRequest, res: Response, next) {
+ const fight = await loadMonsterFromFight(req.player.id);
+ if(!fight) {
+ next();
+ return;
+ }
+
+ res.send(Alert.ErrorAlert(`You are currently in a fight with a ${fight.name}`));
+}
+
app.get('/player/explore', authEndpoint, async (req: AuthRequest, res: Response) => {
const fight = await loadMonsterFromFight(req.player.id);
+ const travelPlan = await getTravelPlan(req.player.id);
let closestTown = req.player.city_id;
+ if(travelPlan) {
+ closestTown = (travelPlan.current_position / travelPlan.total_distance) > 0.5 ? travelPlan.destination_id : travelPlan.source_id;
+ }
+
const equippedItems = await getEquippedItems(req.player.id);
if(fight) {
- // ok lets display the fight screen!
const data: MonsterForFight = {
id: fight.id,
hp: fight.hp,
};
const location = await getMonsterLocation(fight.ref_id);
- res.send(renderPlayerBar(req.player, equippedItems) + renderFightPreRound(data, true, location));
+
+ res.send(renderPlayerBar(req.player, equippedItems) + renderFightPreRound(data, true, location, closestTown));
}
else {
- const travelPlan = await getTravelPlan(req.player.id);
if(travelPlan) {
// traveling!
- const travelPlan = await getTravelPlan(req.player.id);
-
- const closest: number = (travelPlan.current_position / travelPlan.total_distance) > 0.5 ? travelPlan.destination_id : travelPlan.source_id;
-
const chanceToSeeMonster = random(0, 100);
const things: any[] = [];
if(chanceToSeeMonster <= 30) {
- const monster = await getRandomMonster([closest]);
+ const monster = await getRandomMonster([closestTown]);
things.push(monster);
}
res.send(renderPlayerBar(req.player, equippedItems) + renderTravel({
things,
nextAction,
- closestTown: closest,
+ closestTown: closestTown,
walkingText: '',
travelPlan
}));
fight_trigger: fight.fight_trigger
};
- res.send(renderFightPreRound(data, true, location));
+ res.send(renderFightPreRound(data, true, location, location.city_id));
});
app.post('/travel/step', authEndpoint, async (req: AuthRequest, res: Response) => {
}
break;
case 'monster':
- // prompt to return to town and don't let them do anything
- html.push(`<p>You were killed...</p>`);
+ if(roundData.player.hp === 0) {
+ html.push(`<p>You were killed...</p>`);
+ }
html.push('<p><button hx-get="/player/explore" hx-target="#explore">Back to Town</button></p>');
break;
case 'in-progress':
return html;
}
-export function renderFightPreRound(monster: MonsterForFight, displayFightActions: boolean = true, location: LocationWithCity) {
+export function renderFightPreRound(monster: MonsterForFight, displayFightActions: boolean = true, location: LocationWithCity, closestTown: number) {
const hpPercent = Math.floor((monster.hp / monster.maxHp) * 100);
let html = `
+<section id="explore" class="tab active" style="background-image: url('/assets/img/map/${closestTown}.jpeg')" hx-swap-oob="true">
<div class="city-title-wrapper">
<div class="city-title">${location.city_name}</div>
</div>
</div>
<div id="fight-results"></div>
</div>
-</div>`;
+</div>
+</section>
+`;
return html;
}
function renderInventoryItem(item: EquippedItemDetails , action: (item: EquippedItemDetails) => string): string {
return `<div class="store-list">
- <div>
- <img src="${icon(item.icon)}">
+ <div class="inventory-icon" style="background-image: url('${icon(item.icon)}')">
</div>
<div class="details">
<div class="name">${item.name}</div>
</div>
${item.hasOwnProperty('id') ? `<div>${item.cost.toLocaleString()}G</div>` : ''}
</div>
- <div class="store-actions">
+ <div class="inventory-actions">
${action(item)}
</div>
</div>`;
</div>
</div>
</div>
+</section>
`;
return html;
}
return `<div class="progress-bar" id="${id}" style="background: linear-gradient(to right, ${opts.startingColor}, ${opts.endingColor} ${percent}%, transparent ${percent}%, transparent)"
-title="${percent}% - ${current}/${max}">${current}/${max} - ${percent}</div>`;
+title="${percent}% - ${current}/${max}">${current}/${max} - ${percent}%</div>`;
}
export function renderPlayerBar(player: Player, inventory: EquippedItemDetails[]): string {
function renderShopItem(item: (ShopItem & Item), action: (item: (ShopItem & Item)) => string): string {
return `<div class="store-list">
- <div>
- <img src="/assets/img/icons/items/${item.icon_name}" title="${item.name}">
+ <div class="store-icon">
+ <img src="/assets/img/icons/items/${item.icon_name}" title="${item.name}"><div class="store-actions">${action(item)}</div>
</div>
<div class="details">
<div class="name">${item.name}</div>
<div class="requirements">
${item.description}
</div>
- ${item.hasOwnProperty('id') ? `<div>${item.price_per_unit.toLocaleString()}G</div>` : ''}
- </div>
- <div class="store-actions">
- ${action(item)}
+ ${item.hasOwnProperty('id') ? `<div class="store-cost">${item.price_per_unit.toLocaleString()}G</div>` : ''}
</div>
</div>`;
}
${item.boosts.damage_mitigation ? renderStatBoost('MIT', item.boosts.damage_mitigation.toString())+'%' : ''}
${['WEAPON','SPELL'].includes(item.type) ? '' : renderStatBoost('AP', item.maxAp.toString())}
</div>
- ${item.hasOwnProperty('id') ? `<div>${item.cost.toLocaleString()}G</div>` : ''}
+ ${item.hasOwnProperty('id') ? `<div class="store-cost">${item.cost.toLocaleString()}G</div>` : ''}
</div>
`
function renderShopEquipment(item: ShopEquipment, action: (item: ShopEquipment) => string, player: Player): string {
return `<div class="store-list">
- <div>
- <img src="${item.icon ? `/assets/img/icons/equipment/${item.icon}` : 'https://via.placeholder.com/64x64'}">
+ <div class="store-icon" style="background-image: url('${item.icon ? `/assets/img/icons/equipment/${item.icon}` : 'https://via.placeholder.com/64x64'}')">
+ <div class="store-actions">${action(item)}</div>
</div>
${renderEquipmentDetails(item, player)}
- <div class="store-actions">
- ${action(item)}
- </div>
</div>`;
}
}
export function maxHp(constitution: number, playerLevel: number): number {
- return Math.ceil((constitution + playerLevel) * 1.3);
+ return Math.ceil((constitution * 1.7) + (playerLevel * 1.3));
}
export function expToLevel(level: number): number {