Browse Source

Improve damage and test displays

master
Fen Dweller 5 years ago
parent
commit
2d7c1a97f1
7 changed files with 204 additions and 168 deletions
  1. +51
    -0
      src/App.vue
  2. +0
    -46
      src/components/Statblock.vue
  3. +3
    -111
      src/game/combat.ts
  4. +133
    -0
      src/game/combat/tests.ts
  5. +6
    -7
      src/game/creatures/cafat.ts
  6. +2
    -1
      src/game/creatures/wolf.ts
  7. +9
    -3
      src/game/interface.ts

+ 51
- 0
src/App.vue View File

@@ -61,4 +61,55 @@ body, html {
display: flex;
flex-direction: column;
}

.stat-entry {
position: relative;
}

.stat-entry::after {
opacity: 0;
position: absolute;
color: #eee;
font-size: 0pt;
content: attr(data-tooltip);
transition: 0.1s;
pointer-events: none;
left: 0pt;
top: 0pt;
transform: translate(calc(-50% + 16pt), -100%);
background: #555;
padding: 8pt;
border-radius: 8pt;
z-index: 1;
}

.stat-entry:hover::after {
font-size: 18pt;
opacity: 1;
}

.stat-entry::before {
opacity: 0;
position: absolute;
color: #eee;
font-size: 0pt;
content: attr(data-tooltip-full);
pointer-events: none;
left: 0pt;
top: 0pt;
transform: translate(calc(-50% + 16pt), calc(-100% - 18pt - 16pt));
white-space: nowrap;
transition: 0.1s;
background: #555;
padding: 8pt;
border-radius: 8pt;
z-index: 1;
}

.stat-entry:hover::before {
font-size: 12pt;
transition: all 1s cubic-bezier(1, 0, 0.75, 0);
opacity: 1;
}

</style>

+ 0
- 46
src/components/Statblock.vue View File

@@ -85,52 +85,6 @@ a {
justify-content: space-evenly;
user-select: none;
}

.stat-entry::after {
opacity: 0;
position: absolute;
color: #eee;
font-size: 0pt;
content: attr(data-tooltip);
transition: 0.1s;
pointer-events: none;
left: 0pt;
top: 0pt;
transform: translate(calc(-50% + 16pt), -100%);
background: #555;
padding: 8pt;
border-radius: 8pt;
z-index: 1;
}

.stat-entry:hover::after {
font-size: 18pt;
opacity: 1;
}

.stat-entry::before {
opacity: 0;
position: absolute;
color: #eee;
font-size: 0pt;
content: attr(data-tooltip-full);
pointer-events: none;
left: 0pt;
top: 0pt;
transform: translate(calc(-50% + 16pt), calc(-100% - 18pt - 16pt));
white-space: nowrap;
transition: 0.1s;
background: #555;
padding: 8pt;
border-radius: 8pt;
z-index: 1;
}

.stat-entry:hover::before {
font-size: 12pt;
transition: all 1s cubic-bezier(1, 0, 0.75, 0);
opacity: 1;
}
</style>

<style>


+ 3
- 111
src/game/combat.ts View File

@@ -1,7 +1,8 @@
import { Creature, POV, Entity } from './entity'
import { POVPair, POVPairArgs, TextLike, DynText, LiveText } from './language'
import { Container } from './vore'
import { LogEntry, LogLines, CompositeLog, FAElem, LogLine, FormatEntry, FormatOpt } from './interface'
import { LogEntry, LogLines, CompositeLog, FAElem, LogLine, FormatEntry, FormatOpt, PropElem } from './interface'
import { StatTest, StatVigorTest } from './combat/tests'

export enum DamageType {
Pierce = "Pierce",
@@ -70,115 +71,6 @@ export interface CombatTest {
explain: (user: Creature, target: Creature) => LogEntry;
}

function logistic (x0: number, L: number, k: number): (x: number) => number {
return (x: number) => {
return L / (1 + Math.exp(-k * (x - x0)))
}
}

abstract class RandomTest implements CombatTest {
test (user: Creature, target: Creature): boolean {
return Math.random() < this.odds(user, target)
}

abstract odds(user: Creature, target: Creature): number
abstract explain(user: Creature, target: Creature): LogEntry
}

export class StatVigorTest extends RandomTest {
private f: (x: number) => number

constructor (public readonly stat: Stat, k = 0.1) {
super()
this.f = logistic(0, 1, k)
}

odds (user: Creature, target: Creature): number {
let userPercent = 1
let targetPercent = 1

Object.keys(Vigor).forEach(key => {
userPercent *= user.vigors[key as Vigor] / user.maxVigors[key as Vigor]
targetPercent *= target.vigors[key as Vigor] / target.maxVigors[key as Vigor]

userPercent = Math.max(0, userPercent)
targetPercent = Math.max(0, targetPercent)
})

if (userPercent === 0) {
targetPercent *= 4
}

if (targetPercent === 0) {
userPercent *= 4
}

console.log(userPercent, targetPercent, this.f(user.stats[this.stat] * userPercent - target.stats[this.stat] * targetPercent))
return this.f(user.stats[this.stat] * userPercent - target.stats[this.stat] * targetPercent)
}

explain (user: Creature, target: Creature): LogEntry {
const delta: number = user.stats[this.stat] - target.stats[this.stat]
let result: string

if (delta === 0) {
result = 'You and the target have the same ' + this.stat + '.'
} else if (delta < 0) {
result = 'You have ' + delta + ' less ' + this.stat + ' than your foe.'
} else {
result = 'You have ' + delta + ' more ' + this.stat + ' than you foe.'
}

result += ' Your odds of success are ' + (100 * this.odds(user, target)).toFixed(1) + '%'

return new LogLines(result)
}
}

export class StatTest extends RandomTest {
private f: (x: number) => number

constructor (public readonly stat: Stat, k = 0.1) {
super()
this.f = logistic(0, 1, k)
}

odds (user: Creature, target: Creature): number {
return this.f(user.stats[this.stat] - target.stats[this.stat])
}

explain (user: Creature, target: Creature): LogEntry {
const delta: number = user.stats[this.stat] - target.stats[this.stat]
let result: string

if (delta === 0) {
result = 'You and the target have the same ' + this.stat + '.'
} else if (delta < 0) {
result = 'You have ' + delta + ' less ' + this.stat + ' than your foe.'
} else {
result = 'You have ' + delta + ' more ' + this.stat + ' than you foe.'
}

result += ' Your odds of success are ' + (100 * this.odds(user, target)).toFixed(1) + '%'

return new LogLines(result)
}
}

export class ChanceTest extends RandomTest {
constructor (public readonly chance: number) {
super()
}

odds (user: Creature, target: Creature): number {
return this.chance
}

explain (user: Creature, target: Creature): LogEntry {
return new LogLines('You have a flat ' + (100 * this.chance) + '% chance.')
}
}

export class Damage {
readonly damages: DamageInstance[]

@@ -217,7 +109,7 @@ export class Damage {
totals[instance.target] += instance.amount
})

return new FormatEntry(new LogLine(...Object.keys(Vigor).flatMap(key => totals[key as Vigor] === 0 ? [] : [totals[key as Vigor].toFixed(0).toString(), new FAElem(VigorIcons[key as Vigor])])), FormatOpt.DamageInst)
return new FormatEntry(new LogLine(...Object.keys(Vigor).flatMap(key => totals[key as Vigor] === 0 ? [] : [new PropElem(key as Vigor, totals[key as Vigor]), ' '])), FormatOpt.DamageInst)
}
}



+ 133
- 0
src/game/combat/tests.ts View File

@@ -0,0 +1,133 @@
import { CombatTest, Stat, Vigor } from '../combat'
import { Creature } from '../entity'
import { LogEntry, LogLines, PropElem, LogLine } from '../interface'

function logistic (x0: number, L: number, k: number): (x: number) => number {
return (x: number) => {
return L / (1 + Math.exp(-k * (x - x0)))
}
}

abstract class RandomTest implements CombatTest {
test (user: Creature, target: Creature): boolean {
return Math.random() < this.odds(user, target)
}

abstract odds(user: Creature, target: Creature): number
abstract explain(user: Creature, target: Creature): LogEntry
}

export class StatVigorTest extends RandomTest {
private f: (x: number) => number

constructor (public readonly stat: Stat, k = 0.1) {
super()
this.f = logistic(0, 1, k)
}

odds (user: Creature, target: Creature): number {
let userPercent = 1
let targetPercent = 1

Object.keys(Vigor).forEach(key => {
userPercent *= user.vigors[key as Vigor] / user.maxVigors[key as Vigor]
targetPercent *= target.vigors[key as Vigor] / target.maxVigors[key as Vigor]

userPercent = Math.max(0, userPercent)
targetPercent = Math.max(0, targetPercent)
})

if (userPercent === 0) {
targetPercent *= 4
}

if (targetPercent === 0) {
userPercent *= 4
}

console.log(userPercent, targetPercent, this.f(user.stats[this.stat] * userPercent - target.stats[this.stat] * targetPercent))
return this.f(user.stats[this.stat] * userPercent - target.stats[this.stat] * targetPercent)
}

explain (user: Creature, target: Creature): LogEntry {
let result: LogEntry

let userPercent = 1
let targetPercent = 1

Object.keys(Vigor).forEach(key => {
userPercent *= user.vigors[key as Vigor] / user.maxVigors[key as Vigor]
targetPercent *= target.vigors[key as Vigor] / target.maxVigors[key as Vigor]

userPercent = Math.max(0, userPercent)
targetPercent = Math.max(0, targetPercent)
})

if (userPercent === 0) {
targetPercent *= 4
}

if (targetPercent === 0) {
userPercent *= 4
}
const userMod = user.stats[this.stat] * userPercent
const targetMod = target.stats[this.stat] * targetPercent
const delta = (userMod - targetMod)

if (delta === 0) {
result = new LogLine('You and the target have the same effective', new PropElem(this.stat), '.')
} else if (delta < 0) {
result = new LogLine('You effectively have ', new PropElem(this.stat, -delta), ' less than your foe.')
} else {
result = new LogLine('You effectively have ', new PropElem(this.stat, delta), ' more than you foe.')
}

result = new LogLine(result, 'Your odds of success are ' + (100 * this.odds(user, target)).toFixed(1) + '%')

return result
}
}

export class StatTest extends RandomTest {
private f: (x: number) => number

constructor (public readonly stat: Stat, k = 0.1) {
super()
this.f = logistic(0, 1, k)
}

odds (user: Creature, target: Creature): number {
return this.f(user.stats[this.stat] - target.stats[this.stat])
}

explain (user: Creature, target: Creature): LogEntry {
const delta: number = user.stats[this.stat] - target.stats[this.stat]
let result: LogEntry

if (delta === 0) {
result = new LogLine('You and the target have the same ', new PropElem(this.stat), '.')
} else if (delta < 0) {
result = new LogLine('You have ', new PropElem(this.stat, -delta), ' less than your foe.')
} else {
result = new LogLine('You have ', new PropElem(this.stat, delta), ' more than you foe.')
}

result = new LogLine(result, 'Your odds of success are ' + (100 * this.odds(user, target)).toFixed(1) + '%')

return result
}
}

export class ChanceTest extends RandomTest {
constructor (public readonly chance: number) {
super()
}

odds (user: Creature, target: Creature): number {
return this.chance
}

explain (user: Creature, target: Creature): LogEntry {
return new LogLines('You have a flat ' + (100 * this.chance) + '% chance.')
}
}

+ 6
- 7
src/game/creatures/cafat.ts View File

@@ -1,9 +1,8 @@
import { Creature, POV, Entity } from '../entity'
import { Stat, Damage, DamageType, TransferAction, Vigor, StatTest, FeedAction, DigestAction, EatenAction, AttackAction, DamageFormula, ConstantDamageFormula } from '../combat'
import { Stat, Damage, DamageType, TransferAction, Vigor, FeedAction, EatenAction, AttackAction, ConstantDamageFormula } from '../combat'
import { ProperNoun, TheyPronouns, ImproperNoun, POVPair, FemalePronouns, POVPairArgs } from '../language'
import { VoreType, Stomach, InnerStomach, Container, Bowels } from '../vore'
import { VoreType, Stomach, InnerStomach, Container } from '../vore'
import { LogLine, LogLines, LogEntry, FAElem, CompositeLog, ImgElem } from '../interface'
import { Wolf } from '../creatures'

class BellyCrushAction extends AttackAction {
successLines = new POVPairArgs<Entity, Entity, { damage: Damage }>([
@@ -23,8 +22,8 @@ class BellyCrushAction extends AttackAction {

constructor (_damage: Damage) {
super({
calc (user, target) { return _damage.scale(user.bulk / 25) },
describe (user, target) { return new LogLine('Deal ', _damage.scale(user.bulk / 25).renderShort(), ` with your ${user.bulk} `, new FAElem('fas fa-weight-hanging')) }
calc (user) { return _damage.scale(user.bulk / 25) },
describe (user) { return new LogLine('Deal ', _damage.scale(user.bulk / 25).renderShort(), ` with your ${user.bulk} `, new FAElem('fas fa-weight-hanging')) }
})
this.name = 'Belly Crush'
this.desc = 'Use your weight!'
@@ -60,7 +59,7 @@ class BelchAction extends AttackAction {
class CrushAction extends EatenAction {
lines: POVPair<Entity, Entity> = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLine(`You crush ${target.name} `, new FAElem('fas fa-skull'))],
[[POV.Third, POV.First], (user, target) => new CompositeLog(new LogLine(`${user.name.capital} crushes you; ${user.pronouns.subjective} ${user.pronouns.isPlural ? 'belch' : 'belches'} as ${user.pronouns.possessive} gut lets out a fatal CRUNCH `, new FAElem('fas fa-skull')), new ImgElem('./media/cafat/images/crunch.webp'))],
[[POV.Third, POV.First], (user) => new CompositeLog(new LogLine(`${user.name.capital} crushes you; ${user.pronouns.subjective} ${user.pronouns.isPlural ? 'belch' : 'belches'} as ${user.pronouns.possessive} gut lets out a fatal CRUNCH `, new FAElem('fas fa-skull')), new ImgElem('./media/cafat/images/crunch.webp'))],
[[POV.Third, POV.Third], (user, target) => new LogLine(`${user.name.capital} crushes ${target.name}; ${user.pronouns.subjective} ${user.pronouns.isPlural ? 'belch' : 'belches'} as ${user.pronouns.possessive} gut lets out a fatal CRUNCH `, new FAElem('fas fa-skull'))]
])

@@ -120,7 +119,7 @@ export class Cafat extends Creature {

stomach.consumeLines = new POVPair([
[[POV.First, POV.Third], (user, target) => new LogLines(`You devour ${target.name}`)],
[[POV.Third, POV.First], (user, target) => new CompositeLog(new LogLines(`${user.name.capital} devours you`), new ImgElem('./media/cafat/images/stomach.webp'))],
[[POV.Third, POV.First], (user) => new CompositeLog(new LogLines(`${user.name.capital} devours you`), new ImgElem('./media/cafat/images/stomach.webp'))],
[[POV.Third, POV.Third], (user, target) => new LogLines(`${user.name.capital} munches ${target.name.capital}`)]
])
const crush = new CrushAction(lowerStomach)


+ 2
- 1
src/game/creatures/wolf.ts View File

@@ -1,8 +1,9 @@
import { Creature, POV, Entity } from '../entity'
import { Stat, Damage, DamageType, AttackAction, TransferAction, Vigor, StatTest, FeedAction, ConstantDamageFormula } from '../combat'
import { Stat, Damage, DamageType, AttackAction, TransferAction, Vigor, FeedAction, ConstantDamageFormula } from '../combat'
import { MalePronouns, ImproperNoun, POVPair, POVPairArgs } from '../language'
import { LogLine, LogLines } from '../interface'
import { VoreType, Stomach, Bowels } from '../vore'
import { StatTest } from '../combat/tests'

class BiteAction extends AttackAction {
constructor () {


+ 9
- 3
src/game/interface.ts View File

@@ -95,7 +95,7 @@ export class FAElem implements LogEntry {
}

export class PropElem implements LogEntry {
constructor (private value: number, private prop: Stat | Vigor) {
constructor (private prop: Stat | Vigor, private value: number|null = null) {

}

@@ -114,12 +114,18 @@ export class PropElem implements LogEntry {

const span = document.createElement("span")
span.classList.add("stat-entry")
span.textContent = this.value.toString()

if (this.value !== null) {
const numText = Math.round(this.value).toFixed(0) === this.value.toFixed(0) ? this.value.toFixed(0) : this.value.toFixed(1)
span.textContent = numText + ' '
}

new FAElem(cls).render().forEach(elem => {
span.appendChild(elem)
})

span.dataset.tooltip = this.prop
span.dataset["tooltip-full"] = this.prop
span.dataset.tooltipFull = this.prop

return [span]
}


Loading…
Cancel
Save