문서 게임플레이 확장

게임플레이 모드

스킴은 여러분 대신 싸우는 AI일 뿐 아니라 하나의 규칙이 될 수도 있습니다. 여러분은 게임 창에서 직접 플레이하고, 모드는 시작 배치, 몬스터 생성, 보상, 화면의 버튼과 선택 카드, 승패 판정을 맡습니다. openwar3.Mod를 상속하면 파일 하나가 곧 하나의 게임 방식입니다.

AI 스킴에는 두 종류가 있습니다. kind: bot은 여러분 대신 싸우는 AI이고, kind: mod는 하나의 규칙입니다 — 여러분이 게임 창에서 직접 플레이하고, 모드가 과제를 냅니다. 시작 배치를 어떻게 할지, 시간이나 이벤트에 따라 몬스터를 생성할지, 어떤 보상을 줄지, 화면에 어떤 버튼과 선택 카드를 보여 줄지, 언제 승리로 칠지를 정합니다.

모드는 이미 있는 기능만으로 만들어집니다. UI와 입력(클릭할 수 있는 버튼, 카드, 단축키, 지면 클릭), 캔버스(패널, 진행 바, 경로), JASS 채널(유닛 생성, 속성 변경, 아이템 지급), 이벤트 스트림(사망, 레벨 업, 스킬 시전, 채팅).

예제 두 개

Farsight의 “AI 스킴” → “내장”에서 바로 고를 수 있습니다.

모드게임 방식사용하는 기능
영웅 로그라이크 builtin/hero-roguelike여러분에게는 팔라딘 하나뿐이고, 몬스터가 웨이브마다 사방에서 몰려옵니다. 레벨이 오를 때마다 화면 가운데에서 강화 셋 중 하나를 고릅니다(고르는 동안 게임 일시 정지). 10웨이브를 버티면 승리, 영웅이 죽으면 패배g.ui.choice(클릭 가능한 카드 + 일시 정지), hero.levelup / killed / spell.cast 이벤트, 채팅 -help, JASS로 영웅 속성 변경, 아이템 지급
무한 디펜스 builtin/endless-defense몬스터가 반대편 시작 지점에서 지면의 빨간 선을 따라 본진으로 돌진합니다. 한 웨이브를 막을 때마다 금을 받습니다. 화면 버튼을 클릭하거나 F7을 누르면 다음 웨이브를 앞당겨 부르고 보상은 ×1.5. F8을 누른 뒤 왼쪽 버튼으로 지면을 클릭하면 무료 화살탑을 하나 세웁니다(오른쪽 버튼으로 취소)g.ui.button, g.ui.hotkey, g.ui.mouse(지면 클릭 잡기), 캔버스 패널 / 진행 바 / 경로, JASS로 몬스터 생성과 금 지급

두 예제는 각각 150줄 정도이며, 코드는 brains/examples/mod_hero_roguelike.py와 brains/examples/mod_endless_defense.py에 있습니다.

python tools/play.py --bot brains/examples/mod_hero_roguelike.py --inst 9     # 게임을 시작하면 모드가 넘겨받고, 여러분은 게임 창에서 플레이

모드 작성하기

from openwar3 import Mod

class Survive(Mod):
    name = "survive"

    def on_start(self, g):
        super().on_start(g)                        # 싱글플레이 확인 + 컴퓨터 상대 억제
        self.foe = self.wave_player(g)             # 빈 슬롯 플레이어 하나를 "웨이브 플레이어"로: 누구와도 동맹하지 않고 컴퓨터 AI도 없음
        self.every(30, self.wave)                  # 30게임 초마다 한 웨이브(일시 정지 중에는 흐르지 않음)
        g.ui.hotkey("F7", lambda g, ev: self.wave(g))

    def wave(self, g):
        self.spawn_ring(g, self.foe, "ugho", 6, self.home(g), 1400, attack_to=self.home(g))

    def on_event(self, g, ev):
        if ev.kind == "unit.died" and ev.type == "htow":
            self.finish("loss", "본진이 무너졌습니다")

Mod는 Bot에 다음을 더합니다.

메서드 / 속성설명
on_start / on_tick / on_event / on_endBot과 같음. on_start / on_tick을 재정의할 때는 먼저 super()를 호출하세요
every(초, fn, first=) / after(초, fn)게임 시간 기준 타이머. 콜백은 fn(g)
finish(result, reason)이 게임을 끝냄('win' / 'loss' / 'unknown'): 러너가 다음 틱에 멈추고, 화면 가운데에 결과 패널을 그리며, 스킴 전적은 이 값으로 기록
wave_player(g)첫 번째 빈 슬롯 플레이어. 웨이브 플레이어로 사용
spawn_ring(g, 플레이어, 유닛, 개수, 중심, 반지름, attack_to=)원 둘레에 유닛을 생성. 한 웨이브에 수십 개여도 끊기지 않음. JASS 핸들을 반환
alive_of(g, 플레이어) / attack_move_all(g, 플레이어, 지점)어떤 플레이어의 살아 있는 유닛 / 모두 공격 이동(몇 초마다 호출하면 몬스터가 쫓아옴)
home(g) / hud(g, 제목, 줄)아군 본진의 위치 / 오른쪽 위 정보 패널
neutralize_ai = True게임 시작 시 컴퓨터 상대를 억제: 그 유닛을 5초마다 한 번씩 멈추고 금과 목재를 0으로. 대전 맵에는 항상 컴퓨터가 하나 있으므로, 모드가 직접 규칙을 정할 때 방해하지 않게 함
single_player_only = True다른 사람 플레이어가 있으면 실행을 거부(월드를 바꾸는 JASS는 다른 사람과 동기화를 깨뜨림)
linger_s = 6승패가 난 뒤 결과 화면에서 몇 초 머물고 종료

finish()는 Bot에서도 쓸 수 있습니다. 일반 Bot도 스스로 종료를 선언할 수 있습니다.

스킴으로 만들어 공유하기

scheme.json에 "kind": "mod"를 쓰고, 진입 파일에 Mod의 하위 클래스를 하나 정의합니다.

{"id": "survive", "name": "10웨이브 버티기", "kind": "mod", "entry": "survive.py", "class": "Survive"}

모드는 항상 공정 모드를 쓰지 않으며(과제를 내는 심판이므로 맵 전체를 보고 월드를 바꿔야 함), 대전 규칙으로 승패를 판정하지도 않습니다(승패는 finish로 보고). 매니페스트에 fair / judge를 써도 효과가 없습니다. zip 내보내기, 가져오기, 신뢰, 전적은 Bot 스킴과 완전히 같습니다. AI 스킴을 참고하세요. 모드도 코드이므로, 다른 사람의 모드는 처음 실행하기 전에 똑같이 신뢰를 확인해야 합니다.

실측

2026-09-25, 테스트 인스턴스에서:

  • 영웅 로그라이크: 첫 웨이브가 생성되고 오른쪽 위 패널이 갱신됨. 영웅을 3레벨로 올림 → 화면 가운데에 카드가 뜨고 게임 시계가 멈춤. 카드를 두 번 클릭 → 강화 두 번이 적용됨(힘 22 → 27), 시계가 다시 흐름.
  • 무한 디펜스: 패널, 지면의 경로, 버튼이 모두 표시됨. F8 + 지면 클릭 → 본진 옆에 방어탑이 하나 생김. 이번 웨이브를 다 처리하기 전에 버튼 클릭 → “이번 웨이브를 아직 다 처리하지 못했습니다” 안내.

한계

  • 싱글플레이 게임 전용: 유닛 생성과 속성 변경은 JASS 채널을 거치므로 멀티플레이 게임에서는 동기화가 깨집니다. 이는 락스텝 모델 때문이며, 멀티플레이용 게임 방식은 동기화 채널을 기다려야 합니다(로드맵 참고).
  • 모드는 맵 전체를 봅니다 — 과제를 내는 쪽이지 플레이어가 아니기 때문입니다.
  • 대전 맵의 컴퓨터 상대는 “억제”될 뿐 제거되지 않습니다(제거하면 대전 규칙의 승리 판정이 발동합니다).