feat: 턴 당기기, 턴 미루기 사령턴

This commit is contained in:
2025-11-01 16:59:50 +00:00
parent 1492666d3a
commit cde6d86e1b
6 changed files with 326 additions and 2 deletions
@@ -0,0 +1,204 @@
<?php
namespace sammo\Command\Nation;
use \sammo\DB;
use \sammo\Util;
use \sammo\JosaUtil;
use \sammo\General;
use \sammo\DummyGeneral;
use \sammo\ActionLogger;
use \sammo\GameConst;
use \sammo\LastTurn;
use \sammo\GameUnitConst;
use \sammo\Command;
use \sammo\Constraint\Constraint;
use \sammo\Constraint\ConstraintHelper;
use sammo\Enums\GeneralQueryMode;
use sammo\StaticEventHandler;
use function sammo\pullGeneralCommand;
use function sammo\pushGeneralCommand;
class che_행동지시 extends Command\NationCommand
{
static protected $actionName = '행동 지시';
static public $reqArg = true;
protected function argTest(): bool
{
if ($this->arg === null) {
return false;
}
if (!key_exists('isPull', $this->arg)) {
return false;
}
if (!key_exists('amount', $this->arg)) {
return false;
}
if (!key_exists('destGeneralID', $this->arg)) {
return false;
}
$isPull = $this->arg['isPull'];
$amount = $this->arg['amount'];
$destGeneralID = $this->arg['destGeneralID'];
if (!is_numeric($amount)) {
return false;
}
if ($amount <= 0) {
return false;
}
if (!is_bool($isPull)) {
return false;
}
if (!is_int($destGeneralID)) {
return false;
}
if ($destGeneralID <= 0) {
return false;
}
$amount = Util::clamp($amount, 1, 5);
$this->arg = [
'isPull' => $isPull,
'amount' => $amount,
'destGeneralID' => $destGeneralID
];
return true;
}
protected function init()
{
$general = $this->generalObj;
$this->setCity();
$this->setNation();
$this->minConditionConstraints = [
ConstraintHelper::NotBeNeutral(),
ConstraintHelper::OccupiedCity(),
ConstraintHelper::BeChief(),
ConstraintHelper::SuppliedCity(),
];
}
protected function initWithArg()
{
$destGeneral = General::createObjFromDB($this->arg['destGeneralID']);
$this->setDestGeneral($destGeneral);
if($this->arg['destGeneralID'] == $this->getGeneral()->getID()){
$this->fullConditionConstraints=[
ConstraintHelper::AlwaysFail('본인입니다')
];
return;
}
$this->fullConditionConstraints = [
ConstraintHelper::NotBeNeutral(),
ConstraintHelper::OccupiedCity(),
ConstraintHelper::BeChief(),
ConstraintHelper::SuppliedCity(),
ConstraintHelper::ExistsDestGeneral(),
ConstraintHelper::FriendlyDestGeneral()
];
}
public function getCost(): array
{
return [0, 0];
}
public function getPreReqTurn(): int
{
return 0;
}
public function getPostReqTurn(): int
{
return 0;
}
public function getBrief(): string
{
$isPull = $this->arg['isPull'];
$amount = $this->arg['amount'];
$actSpecificName = $isPull ? '당기기' : '미루기';
$destGeneral = $this->destGeneralObj;
return "{$destGeneral->getName()}{$amount}{$actSpecificName}";
}
public function run(\Sammo\RandUtil $rng): bool
{
if (!$this->hasFullConditionMet()) {
throw new \RuntimeException('불가능한 커맨드를 강제로 실행 시도');
}
$db = DB::db();
$general = $this->generalObj;
$date = $general->getTurnTime($general::TURNTIME_HM);
$isPull = $this->arg['isPull'];
$amount = $this->arg['amount'];
$destGeneral = $this->destGeneralObj;
if($isPull){
pullGeneralCommand($destGeneral->getID(), $amount);
}
else{
pushGeneralCommand($destGeneral->getID(), $amount);
}
$logger = $general->getLogger();
$actDestText = $isPull ? '당겼습니다.' : '미루었습니다.';
$actText = $isPull ? '당기도록' : '미루도록';
$destGeneral->getLogger()->pushGeneralActionLog("<Y>{$general->getName()}</>의 지시로 <C>{$amount}</>턴을 {$actDestText}", ActionLogger::PLAIN);
$logger->pushGeneralActionLog("<Y>{$destGeneral->getName()}</>에게 <C>{$amount}</>턴을 {$actText} 지시했습니다. <1>$date</>");
$this->setResultTurn(new LastTurn(static::getName(), $this->arg));
StaticEventHandler::handleEvent($this->generalObj, $this->destGeneralObj, $this::class, $this->env, $this->arg ?? []);
$general->applyDB($db);
$destGeneral->applyDB($db);
return true;
}
public function exportJSVars(): array
{
$db = DB::db();
$nationID = $this->getNationID();
$troops = Util::convertArrayToDict($db->query('SELECT * FROM troop WHERE nation=%i', $nationID), 'troop_leader');
$destRawGenerals = Util::convertArrayToDict($db->queryAllLists('SELECT no,name,officer_level,npc,gold,rice,leadership,strength,intel,city,crew,train,atmos,troop FROM general WHERE nation = %i ORDER BY npc,binary(name)', $nationID), 0);
if($destRawGenerals){
foreach ($db->queryAllLists(
'SELECT general_id, brief FROM general_turn WHERE general_id IN %li AND turn_idx = 0',
array_keys($destRawGenerals)
) as [$generalID, $brief]) {
if (!key_exists($generalID, $destRawGenerals)) {
continue;
}
$destRawGenerals[$generalID][] = $brief;
}
}
return [
'procRes' => [
'troops' => $troops,
'generals' => array_values($destRawGenerals),
'generalsKey' => ['no', 'name', 'officerLevel', 'npc', 'gold', 'rice', 'leadership', 'strength', 'intel', 'cityID', 'crew', 'train', 'atmos', 'troopID', 'turn0Brief'],
'cities' => \sammo\JSOptionsForCities(),
'amountGuide' => [1, 2, 3, 4, 5],
'minAmount' => 1,
'maxAmount' => 5,
]
];
}
}
+1
View File
@@ -384,6 +384,7 @@ class GameConstBase
'che_포상',
'che_몰수',
'che_부대탈퇴지시',
'che_행동지시',
],
'외교' => [
'che_물자원조',
-1
View File
@@ -10,7 +10,6 @@ import { default as che_장수대상임관 } from "./che_장수대상임관.vue"
import { default as che_징병 } from "./che_징병.vue";
import { default as che_헌납 } from "./che_헌납.vue";
import { default as cr_건국 } from "./cr_건국.vue";
import { default as ProcessCity } from "../ProcessCity.vue";
import { default as ProcessGeneralAmount } from "../ProcessGeneralAmount.vue";
@@ -0,0 +1,118 @@
<template>
<TopBackBar v-model:searchable="searchable" :title="commandName" :type="procEntryMode" />
<div class="bg0">
<div>장수에게 턴을 당기거라 미루라고 지시합니다.</div>
<div class="row">
<div class="col-12 col-lg-5">
장수 :
<SelectGeneral
v-model="selectedGeneralID"
:cities="citiesMap"
:generals="generalList"
:textHelper="textHelpGeneral"
:searchable="searchable"
/>
</div>
<div class="col-2 col-lg-2">
:
<b-button-group>
<b-button :pressed="isPull" @click="isPull = true"> 당기기 </b-button>
<b-button :pressed="!isPull" @click="isPull = false"> 미루기 </b-button>
</b-button-group>
</div>
<div class="col-7 col-lg-3">
<SelectAmount v-model="amount" :amountGuide="amountGuide" :maxAmount="maxAmount" :minAmount="minAmount" />
</div>
<div class="col-3 col-lg-2 d-grid">
<b-button variant="primary" @click="submit">
{{ commandName }}
</b-button>
</div>
</div>
</div>
<BottomBar :title="commandName" :type="procEntryMode" />
</template>
<script lang="ts">
declare const procRes: {
distanceList: Record<number, number[]>;
cities: [number, string][];
generals: procGeneralRawItemList;
generalsKey: procGeneralKey[];
minAmount: number;
maxAmount: number;
amountGuide: number[];
};
declare const staticValues: {
commandName: string;
entryInfo: ["General" | "Nation", unknown];
};
</script>
<script setup lang="ts">
import SelectGeneral from "@/processing/SelectGeneral.vue";
import SelectAmount from "@/processing/SelectAmount.vue";
import { ref } from "vue";
import { unwrap } from "@/util/unwrap";
import type { Args } from "@/processing/args";
import TopBackBar from "@/components/TopBackBar.vue";
import BottomBar from "@/components/BottomBar.vue";
import {
convertGeneralList,
getProcSearchable,
type procGeneralItem,
type procGeneralKey,
type procGeneralRawItemList,
} from "@/processing/processingRes";
import { getNPCColor } from "@/utilGame";
const citiesMap = ref(
new Map<
number,
{
name: string;
info?: string;
}
>()
);
for (const [id, name] of procRes.cities) {
citiesMap.value.set(id, { name });
}
const generalList = convertGeneralList(procRes.generalsKey, procRes.generals);
const amount = ref(1);
const isPull = ref(true);
const selectedGeneralID = ref(generalList[0].no);
type procGeneralItemWithTurn0Brief = procGeneralItem & {
turn0Brief: string;
};
function textHelpGeneral(_gen: procGeneralItem): string {
const gen = _gen as procGeneralItemWithTurn0Brief;
const nameColor = getNPCColor(gen.npc);
const name = nameColor ? `<span style="color:${nameColor}">${gen.name}</span>` : gen.name;
return `${name} [${citiesMap.value.get(unwrap(gen.cityID))?.name}] (${gen.leadership}/${gen.strength}/${
gen.intel
}) (${gen.turn0Brief})`;
}
async function submit(e: Event) {
const event = new CustomEvent<Args>("customSubmit", {
detail: {
amount: amount.value,
isPull: isPull.value,
destGeneralID: selectedGeneralID.value,
},
});
unwrap(e.target).dispatchEvent(event);
}
const { commandName, entryInfo } = staticValues;
const searchable = getProcSearchable();
const procEntryMode: "chief" | "normal" = entryInfo[0] == "Nation" ? "chief" : "normal";
const { minAmount, maxAmount, amountGuide } = procRes;
</script>
+2
View File
@@ -3,6 +3,7 @@ import { default as che_국호변경 } from "./che_국호변경.vue";
import { default as che_물자원조 } from "./che_물자원조.vue";
import { default as che_불가침제의 } from "./che_불가침제의.vue";
import { default as che_피장파장 } from "./che_피장파장.vue";
import { default as che_행동지시 } from "./che_행동지시.vue";
import { default as cr_인구이동 } from "./cr_인구이동.vue";
@@ -33,6 +34,7 @@ export const commandMap: Record<string, typeof ProcessNation | typeof ProcessCit
che_피장파장,
che_허보: ProcessCity,
cr_인구이동,
che_행동지시,
}
/*
+1 -1
View File
@@ -13,7 +13,7 @@ const intArgs = [
] as const;
const booleanArgs = [
'isGold', 'buyRice',
'isGold', 'buyRice', 'isPull',
] as const;
const integerArrayArgs = [