This commit is contained in:
a 2022-08-09 20:39:44 -05:00
parent 5ffa4bffc2
commit 090e6f0222
11 changed files with 344 additions and 48 deletions

6
Makefile Normal file
View File

@ -0,0 +1,6 @@
build:
docker build -t cr.aaaaa.news/lto:latest .
push:
docker push cr.aaaaa.news/lto:latest

View File

@ -8,9 +8,9 @@ import OrderDisplay from "./components/OrderDisplay.vue";
loadStore() loadStore()
</script> </script>
<template> <template>
<OrderDisplay/>
<div class="parent"> <div class="parent">
<div class="splash"> <div class="splash">
<Login />
</div> </div>
<div class="main"> <div class="main">
<CharacterInventory /> <CharacterInventory />
@ -22,7 +22,7 @@ loadStore()
<CharacterRoulette /> <CharacterRoulette />
</div> </div>
<div class="login"> <div class="login">
<Login /> <OrderDisplay/>
</div> </div>
</div> </div>
</template> </template>
@ -35,7 +35,6 @@ loadStore()
text-align: center; text-align: center;
color: #2c3e50; color: #2c3e50;
height: 100vh; height: 100vh;
overflow-y: hidden;
} }
.handsontable th { .handsontable th {
border-right: 0px !important; border-right: 0px !important;
@ -84,6 +83,7 @@ loadStore()
grid-column-gap: 0px; grid-column-gap: 0px;
grid-row-gap: 0px; grid-row-gap: 0px;
overflow: hidden; overflow: hidden;
display: scroll;
} }
.splash { .splash {

View File

@ -55,13 +55,11 @@ const selectCharacter = () => {
</script> </script>
<script lang="ts"> <script lang="ts">
import log from 'loglevel'; import { defineProps, ref, watch} from 'vue';
import { defineComponent, computed, PropType, defineProps, defineEmits, ref, watch, onMounted} from 'vue';
import { getLTOState, LTOApi, LTOApiv0 } from '../lib/lifeto'; import { getLTOState, LTOApi, LTOApiv0 } from '../lib/lifeto';
import { LoginHelper, Session } from '../lib/session';
import { JobNumberToString } from '../lib/trickster'; import { JobNumberToString } from '../lib/trickster';
import { storage } from '../session_storage'; import { storage } from '../session_storage';
import { useStore, useStoreRef } from '../state/state'; import { useStoreRef } from '../state/state';
</script> </script>
<style> <style>
@ -73,16 +71,30 @@ import { useStore, useStoreRef } from '../state/state';
grid-template-rows: repeat(7, 1fr); grid-template-rows: repeat(7, 1fr);
grid-column-gap: 0px; grid-column-gap: 0px;
grid-row-gap: 0px; grid-row-gap: 0px;
margin-right: 10px; margin-right: 5px;
margin-left: 10px; margin-left: 5px;
width: 165px;
font-size: 14px;
} }
.cc_div1 { grid-area: 1 / 1 / 5 / 6; } .cc_div1 { grid-area: 1 / 1 / 5 / 6; }
.cc_div2 { grid-area: 5 / 2 / 6 / 3; } .cc_div2 {
.cc_div3 { grid-area: 5 / 4 / 6 / 5; } text-align: left;
.cc_div4 { grid-area: 6 / 4 / 7 / 5; } grid-area: 5 / 2 / 6 / 3; }
.cc_div3 {
text-align: right;
grid-area: 5 / 4 / 6 / 5; }
.cc_div4 {
text-align: right;
grid-area: 6 / 4 / 7 / 5; }
.cc_div5 { grid-area: 1 / 1 / 8 / 6; } .cc_div5 { grid-area: 1 / 1 / 8 / 6; }
.cc_div6 { grid-area: 7 / 1 / 8 / 3; } .cc_div6 { grid-area: 7 / 1 / 8 / 3; }
.cc_div7 { grid-area: 7 / 4 / 8 / 6; } .cc_div7 {
font-size: 12px;
text-align:right;
padding-right: 8px;
grid-area: 7 / 4 / 8 / 6;
}
</style> </style>

View File

@ -55,6 +55,7 @@ const send_orders = () => {
const origin = activeTable const origin = activeTable
const pending:OrderDetails[] = []; const pending:OrderDetails[] = [];
for(const row of dat) { for(const row of dat) {
try{
const nm = Number(row[idxNumber].replace("x","")) const nm = Number(row[idxNumber].replace("x",""))
const target = (row[idxTarget] as string).replaceAll("-","").trim() const target = (row[idxTarget] as string).replaceAll("-","").trim()
if(!isNaN(nm) && nm > 0 && target.length > 0){ if(!isNaN(nm) && nm > 0 && target.length > 0){
@ -66,6 +67,8 @@ const send_orders = () => {
} }
pending.push(info) pending.push(info)
} }
}catch(e){
}
} }
log.debug("OrderDetails", pending) log.debug("OrderDetails", pending)
for(const d of pending){ for(const d of pending){

View File

@ -48,7 +48,7 @@ import { saveStore, useStoreRef } from '../state/state';
#character_roulette { #character_roulette {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: normal;
align-items: center; align-items: center;
overflow-x: scroll; overflow-x: scroll;
width: 1000px; width: 1000px;

View File

@ -3,7 +3,7 @@ import { TricksterAccount, TricksterInventory } from "../trickster"
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import axios, { AxiosRequestConfig, AxiosResponse } from "axios"; import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
export const BankEndpoints = ["internal-xfer-item", "bank-item"] as const export const BankEndpoints = ["internal-xfer-item", "bank-item", "sell-item","buy-from-order","cancel-order"] as const
export type BankEndpoint = typeof BankEndpoints[number] export type BankEndpoint = typeof BankEndpoints[number]
export interface LTOApi { export interface LTOApi {

View File

@ -1,14 +1,14 @@
import { Axios, AxiosResponse } from "axios" import { Axios, AxiosResponse, Method } from "axios"
import log from "loglevel" import log, { debug } from "loglevel"
import { bank_endpoint, Session } from "../session" import { bank_endpoint, EndpointCreator, market_endpoint, Session } from "../session"
import { dummyChar, TricksterAccount, TricksterInventory, TricksterItem, TricksterWallet } from "../trickster" import { dummyChar, TricksterAccount, TricksterInventory, TricksterItem, TricksterWallet } from "../trickster"
import { BankEndpoint, LTOApi } from "./api" import { BankEndpoint, LTOApi } from "./api"
export const pathIsBank = (path:string):boolean => { export const pathIsBank = (path:string):boolean => {
if(!path.includes("/")) { if(path.includes("/")) {
return true
}
return false return false
}
return true
} }
export const splitPath = (path:string):[string,string]=>{ export const splitPath = (path:string):[string,string]=>{
@ -28,7 +28,17 @@ export class LTOApiv0 implements LTOApi {
} }
BankAction = async <T,D>(e: BankEndpoint, t: T):Promise<D> => { BankAction = async <T,D>(e: BankEndpoint, t: T):Promise<D> => {
return this.s.request("POST",e,t,bank_endpoint).then((x)=>{ let VERB:Method | "POSTFORM" = "POST"
let endpoint:EndpointCreator = bank_endpoint
switch(e){
case "buy-from-order":
case "cancel-order":
endpoint = market_endpoint
case "sell-item":
VERB = "POSTFORM"
default:
}
return this.s.request(VERB as any,e,t,endpoint).then((x)=>{
return x.data return x.data
}) })
} }
@ -42,15 +52,16 @@ export class LTOApiv0 implements LTOApi {
let name = "bank" let name = "bank"
let id = 0 let id = 0
let galders = 0 let galders = 0
if(pathIsBank("char_path")){ if(pathIsBank(char_path)){
let [char, val] = Object.entries(o.characters)[0] as [string,any] let [char, val] = Object.entries(o.characters)[0] as [string,any]
name = val.name name = val.name
id = Number(char) id = Number(char)
galders = val.galders as number galders = 0
}else { }else {
id = o.id let [char, val] = Object.entries(o.characters)[0] as [string,any]
name = o.name name = val.name
galders = o.galders id = Number(char)
galders = val.galders
} }
let out = { let out = {
name, name,
@ -71,8 +82,9 @@ export class LTOApiv0 implements LTOApi {
return ans.data.map((x:any)=>{ return ans.data.map((x:any)=>{
return { return {
name: x.name, name: x.name,
characters: [{id: x.id, path:x.name, name: x.name+'/bank', class:-8, base_job: -8, current_job: -8},...Object.values(x.characters).map((z:any)=>{ characters: [{id: x.id,account_id:x.id, path:x.name, name: x.name+'/bank', class:-8, base_job: -8, current_job: -8},...Object.values(x.characters).map((z:any)=>{
return { return {
account_id: x.id,
id: z.id, id: z.id,
name: z.name, name: z.name,
path: x.name+"/"+z.name, path: x.name+"/"+z.name,

View File

@ -15,6 +15,9 @@ export interface TxnDetails {
origin_path:string origin_path:string
target_path:string target_path:string
origin_account:string
target_account:string
} }
@ -207,7 +210,7 @@ export class BankItem extends BasicOrder{
origin_item.item_count = origin_item.item_count - this.details?.count! origin_item.item_count = origin_item.item_count - this.details?.count!
r.dirty.value++ r.dirty.value++
}else { }else {
throw x.msg ? "unknown error" : "" throw x.msg ? x.msg : "unknown error"
} }
}) })
.catch((e)=>{ .catch((e)=>{
@ -224,3 +227,244 @@ export class BankItem extends BasicOrder{
return this return this
} }
} }
export interface PrivateMarketRequest {
item_uid:string
qty:string
account:string
currency:string
price:number
private:number
}
export interface PrivateMarketResponse extends BasicResponse {}
export class PrivateMarket extends BasicOrder{
order_type = "PrivateMarket";
originalRequest:PrivateMarketRequest
originalResponse?:PrivateMarketResponse
listingId?: string
listingHash?: string
constructor(details:TxnDetails) {
super(details)
this.originalRequest = {
item_uid: details.item_uid,
qty: details.count.toString(),
account: details.origin_account,
private: 1,
currency: "0",
price: 1,
}
}
async tick(r:RefStore, api:LTOApi):Promise<void> {
if(this.state !== "PENDING" ){
return
}
this.mark("WORKING")
return api.BankAction<PrivateMarketRequest, PrivateMarketResponse>("sell-item",this.originalRequest)
.then((x)=>{
debug("PrivateMarket",x)
if(x.status == 200){
this.stage = 1
this.originalResponse = x
this.mark("SUCCESS")
this.listingId = x.data["listing_id"]
this.listingHash = x.data["hash"]
try{
const origin_item = r.invs.value.get(this.details?.origin_path!)!.items[this.details?.item_uid!]!
origin_item.item_count = origin_item.item_count - this.details?.count!
r.dirty.value++
}catch(e){
}
}else {
throw x.msg ? x.msg : "unknown error"
}
})
.catch((e)=>{
this.stage = 1
this.err = e
this.mark("ERROR")
})
}
parse(i:any):PrivateMarket {
super.parse(i)
this.originalRequest = i.originalRequest
this.originalResponse = i.originalResponse
this.listingId = i.listingId
this.listingHash = i.listingHash
return this
}
}
export interface MarketMoveRequest {
listing_id?: string
qty:string
account:string
character: string
}
export interface MarketMoveResponse extends BasicResponse {
item_uid: string
}
export class MarketMove extends PrivateMarket {
order_type = "MarketMove";
moveRequest:MarketMoveRequest
moveResponse?:MarketMoveResponse
moveStage:number
moveState: TxnState
newUid: string
constructor(details:TxnDetails) {
super(details)
this.moveStage = 0
this.moveState = "PENDING"
this.newUid = ""
this.moveRequest = {
qty: details.count.toString(),
account: details.target_account,
character: (details.target_path.includes("/")) ? details.target : "0" ,
listing_id: "", // not initially populated
}
}
async tick(r:RefStore, api:LTOApi):Promise<void> {
try {
await super.tick(r, api)
}catch(e){
return
}
switch(super.status()) {
case "SUCCESS":
break;
case "ERROR":
this.moveState = "ERROR"
return
default:
return
}
if(this.moveState !== "PENDING" ){
return
}
this.moveRequest.listing_id = `${this.listingId}-${this.listingHash}`
this.moveState = "WORKING"
return api.BankAction<MarketMoveRequest, MarketMoveResponse>("buy-from-order",this.moveRequest)
.then((x)=>{
debug("MarketMove",x)
this.moveResponse = x
if(x.status == 200){
this.moveStage = 1
this.moveState = "SUCCESS"
this.newUid = x.item_uid
r.dirty.value++
}else {
throw x ? x : "unknown error"
}
})
.catch((e)=>{
this.moveStage = 1
this.err = e
this.moveState = "ERROR"
})
}
progress():[number,number]{
return [this.stage + this.moveStage, 2]
}
status():string {
return this.moveState
}
parse(i:any):MarketMove {
super.parse(i)
this.moveRequest = i.moveRequest
this.moveResponse = i.moveResponse
this.moveState = i.moveState
this.moveStage = i.moveStage
return this
}
}
export class MarketMoveToChar extends MarketMove {
order_type = "MarketMoveToChar";
charRequest:InternalXferRequest
charResponse?:InternalXferResponse
charStage:number
charState: TxnState
constructor(details:TxnDetails) {
super(details)
this.charStage = 0
this.charState = "PENDING"
this.charRequest = {
item_uid: "",
qty: details.count.toString(),
new_char: details.target,
account: details.target_account,
}
}
async tick(r:RefStore, api:LTOApi):Promise<void> {
try {
await super.tick(r, api)
}catch(e){
return
}
switch(super.status()) {
case "SUCCESS":
break;
case "ERROR":
this.charState = "ERROR"
return
default:
return
}
if(this.charState !== "PENDING" ){
return
}
this.charState = "WORKING"
this.charRequest.item_uid = this.newUid
return api.BankAction<InternalXferRequest, InternalXferResponse>("internal-xfer-item",this.charRequest)
.then((x)=>{
debug("MarketMoveToChar",x)
this.charResponse = x
if(x.status == 200){
this.charStage = 1
this.charState = "SUCCESS"
}else {
throw x ? x : "unknown error"
}
})
.catch((e)=>{
this.charStage = 1
this.err = e
this.charState = "ERROR"
})
}
progress():[number,number]{
return [this.stage +this.moveStage+ this.charStage, 3]
}
status():string {
return this.charState
}
parse(i:any):MarketMoveToChar {
super.parse(i)
this.charRequest = i.charRequest
this.charResponse = i.charResponse
this.charState = i.charState
this.charStage = i.charStage
return this
}
}

View File

@ -1,10 +1,8 @@
import log from "loglevel";
import { Ref } from "vue";
import { RefStore } from "../../state/state"; import { RefStore } from "../../state/state";
import { JsonLoadable, Serializable } from "../storage"; import { Serializable } from "../storage";
import { LTOApi } from "./api"; import { LTOApi } from "./api";
import { pathIsBank, splitPath } from "./lifeto"; import { pathIsBank, splitPath } from "./lifeto";
import { BankItem, BankXfer, InternalXfer, InvalidOrder, Order, TxnDetails } from "./order"; import { BankItem, InternalXfer, InvalidOrder, MarketMove, Order,MarketMoveToChar, TxnDetails } from "./order";
export interface OrderDetails { export interface OrderDetails {
item_uid: string | "galders" item_uid: string | "galders"
@ -23,7 +21,7 @@ export class OrderTracker implements Serializable<OrderTracker> {
let hasDirty = false let hasDirty = false
console.log("ticking") console.log("ticking")
for(const [id, order] of Object.entries(this.orders)) { for(const [id, order] of Object.entries(this.orders)) {
if(order.state == "SUCCESS" || order.state == "ERROR") { if(order.status() == "SUCCESS" || order.status() == "ERROR") {
console.log("finished order", order) console.log("finished order", order)
hasDirty = true hasDirty = true
delete this.orders[id] delete this.orders[id]
@ -49,7 +47,7 @@ export class OrderTracker implements Serializable<OrderTracker> {
let newOrder:Order | undefined = undefined let newOrder:Order | undefined = undefined
console.log("loading", o) console.log("loading", o)
if(o.details){ if(o.details){
if(o.state == "SUCCESS" || o.state == "ERROR") { if(o.status() == "SUCCESS" || o.status() == "ERROR") {
continue continue
} }
switch(o.order_type) { switch(o.order_type) {
@ -59,6 +57,11 @@ export class OrderTracker implements Serializable<OrderTracker> {
case "BankItem": case "BankItem":
newOrder = new BankItem(o.details).parse(o) newOrder = new BankItem(o.details).parse(o)
break; break;
case "MarketMove":
newOrder = new MarketMove(o.details).parse(o)
case "MarketMoveToChar":
newOrder = new MarketMoveToChar(o.details).parse(o)
break;
case "InvalidOrder": case "InvalidOrder":
newOrder = new InvalidOrder("").parse(o) newOrder = new InvalidOrder("").parse(o)
break; break;
@ -106,7 +109,10 @@ export class OrderSender {
bank_to_bank(o:OrderDetails): Order{ bank_to_bank(o:OrderDetails): Order{
const origin = this.r.chars.value.get(o.origin_path) const origin = this.r.chars.value.get(o.origin_path)
const target = this.r.chars.value.get(o.target_path) const target = this.r.chars.value.get(o.target_path)
return notSupported if(!(origin && target)) {
return notFound
}
return new MarketMove(this.transformInternalOrder(o))
} }
bank_to_user(o:OrderDetails): Order{ bank_to_user(o:OrderDetails): Order{
// get the uid of the bank // get the uid of the bank
@ -117,9 +123,9 @@ export class OrderSender {
} }
const [account, name] = splitPath(target.path) const [account, name] = splitPath(target.path)
if(account != origin.path) { if(account != origin.path) {
return notSupported return new MarketMoveToChar(this.transformInternalOrder(o))
} }
return new InternalXfer(this.transformOrder(o)) return new InternalXfer(this.transformInternalOrder(o))
} }
user_to_bank(o:OrderDetails): Order{ user_to_bank(o:OrderDetails): Order{
const origin = this.r.chars.value.get(o.origin_path) const origin = this.r.chars.value.get(o.origin_path)
@ -129,17 +135,20 @@ export class OrderSender {
} }
const [account, name] = splitPath(origin.path) const [account, name] = splitPath(origin.path)
if(account != target.path) { if(account != target.path) {
return notSupported return new MarketMove(this.transformInternalOrder(o))
} }
return new BankItem(this.transformOrder(o)) return new BankItem(this.transformInternalOrder(o))
} }
user_to_user(o:OrderDetails): Order{ user_to_user(o:OrderDetails): Order{
const origin = this.r.chars.value.get(o.origin_path) const origin = this.r.chars.value.get(o.origin_path)
const target = this.r.chars.value.get(o.target_path) const target = this.r.chars.value.get(o.target_path)
return notSupported if(!(origin && target)) {
return notFound
}
return new MarketMoveToChar(this.transformInternalOrder(o))
} }
transformOrder(o:OrderDetails):TxnDetails { private transformInternalOrder(o:OrderDetails):TxnDetails {
const origin = this.r.chars.value.get(o.origin_path)! const origin = this.r.chars.value.get(o.origin_path)!
const target = this.r.chars.value.get(o.target_path)! const target = this.r.chars.value.get(o.target_path)!
return { return {
@ -149,6 +158,8 @@ export class OrderSender {
count: o.count, count: o.count,
origin_path: o.origin_path, origin_path: o.origin_path,
target_path: o.target_path, target_path: o.target_path,
origin_account: origin.account_id.toString(),
target_account: target.account_id.toString(),
} }
} }
} }

View File

@ -7,6 +7,7 @@ export const SITE_ROOT = "/lifeto/"
export const API_ROOT = "api/lifeto/" export const API_ROOT = "api/lifeto/"
export const BANK_ROOT = "item-manager-action/" export const BANK_ROOT = "item-manager-action/"
export const MARKET_ROOT = "marketplace-api/"
const login_endpoint = (name:string)=>{ const login_endpoint = (name:string)=>{
return SITE_ROOT + name return SITE_ROOT + name
@ -18,12 +19,17 @@ export const bank_endpoint = (name:string):string =>{
return SITE_ROOT+BANK_ROOT + name return SITE_ROOT+BANK_ROOT + name
} }
export const market_endpoint = (name:string):string =>{
return SITE_ROOT+MARKET_ROOT+ name
}
export const EndpointCreators = [ export const EndpointCreators = [
api_endpoint, api_endpoint,
bank_endpoint, bank_endpoint,
market_endpoint,
] ]
type EndpointCreator = typeof EndpointCreators[number] export type EndpointCreator = typeof EndpointCreators[number]
export interface Session { export interface Session {
user:string user:string
@ -32,8 +38,6 @@ export interface Session {
request:(verb:Method,url:string,data:any,c?:EndpointCreator)=>Promise<any> request:(verb:Method,url:string,data:any,c?:EndpointCreator)=>Promise<any>
} }
export class LoginHelper { export class LoginHelper {
user:string user:string
pass:string pass:string
@ -66,7 +70,7 @@ export class LogoutHelper{
return axios.get(login_endpoint("logout"),{withCredentials:false}).catch((e)=>{}) return axios.get(login_endpoint("logout"),{withCredentials:false}).catch((e)=>{})
} }
} }
const sleep= async(ms:number)=> { const sleep = async(ms:number)=> {
return new Promise(resolve => setTimeout(resolve, ms)) return new Promise(resolve => setTimeout(resolve, ms))
} }
@ -86,6 +90,9 @@ export class TokenSession implements Session {
case "post": case "post":
promise = axios.post(c(url),data,this.genHeaders()) promise = axios.post(c(url),data,this.genHeaders())
break; break;
case "postform":
promise = axios.postForm(c(url),data)
break;
case "postraw": case "postraw":
const querystring = qs.stringify(data) const querystring = qs.stringify(data)
promise = axios.post(c(url),querystring,this.genHeaders()) promise = axios.post(c(url),querystring,this.genHeaders())

View File

@ -28,6 +28,7 @@ export interface TricksterAccount {
} }
export interface Identifier { export interface Identifier {
account_id: number
id: number id: number
name: string name: string
path: string path: string