294 lines
10 KiB
Python
294 lines
10 KiB
Python
# This file is part of MarMOTS.
|
|
#
|
|
# MarMOTS is free software: you can redistribute it and/or modify it under the terms of the GNU Affero
|
|
# General Public License as published by the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# MarMOTS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
|
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
|
|
# Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License along with MarMOTS. If not,
|
|
# see <https://www.gnu.org/licenses/>.
|
|
#
|
|
# Copyright 2020 Jeremy Penner
|
|
|
|
import ansi_cython as ansi
|
|
from tpers import TPrs, Partial
|
|
from scripting import Vm
|
|
from basetoken import AutoJoiner, AutoKiller, MsgScroller, Mi, MiButton, MiTypein, MiMenu, OvPopup
|
|
from basetoken import LeaveJoinToken, Selor
|
|
from util import RgstWrapped
|
|
from engine import Game, Token, TokenClient, Event
|
|
from datetime import datetime
|
|
import traceback
|
|
|
|
|
|
class Chl(object):
|
|
def __init__(self, client, st):
|
|
self.user = client.cld.user
|
|
self.st = st.rstrip()
|
|
|
|
class ChlMsg(Chl):
|
|
def StUser(self):
|
|
return "<" + self.user + ">"
|
|
def Col(self):
|
|
return ansi.WHITE
|
|
|
|
class ChlAct(Chl):
|
|
def StUser(self):
|
|
return "* " + self.user
|
|
def Col(self):
|
|
return ansi.MAGENTA
|
|
|
|
class ChlSys(Chl):
|
|
def StUser(self):
|
|
return self.user
|
|
def Col(self):
|
|
return ansi.BLUE
|
|
|
|
class Chat(TPrs):
|
|
def InitTransient(self):
|
|
TPrs.InitTransient(self)
|
|
self.cchl = 100
|
|
self.MakeTransient("game", None)
|
|
self.MakeTransient("evChat", None)
|
|
|
|
def InitPersistent(self):
|
|
TPrs.InitPersistent(self)
|
|
self.rgchl = []
|
|
|
|
def AttachToGame(self, game):
|
|
with self.SetTransiently():
|
|
self.game = game
|
|
self.evChat = Event(game)
|
|
|
|
def DetachFromGame(self):
|
|
with self.SetTransiently():
|
|
self.game = None
|
|
self.evChat = None
|
|
|
|
def Add(self, chl):
|
|
if len(chl.st) > 0:
|
|
self.rgchl.insert(0, chl)
|
|
if len(self.rgchl) > self.cchl:
|
|
self.rgchl = self.rgchl[:self.cchl]
|
|
self.evChat.fire(chl)
|
|
|
|
def SendMsg(self, client, st):
|
|
if st.lower().startswith("/me ") and len(st.rstrip()) > 3:
|
|
self.Add(ChlAct(client, st[4:]))
|
|
else:
|
|
self.Add(ChlMsg(client, st))
|
|
|
|
def CChl(self):
|
|
return len(self.rgchl)
|
|
|
|
def CUsers(self):
|
|
return len(self.game.RgclientConnected())
|
|
|
|
def DrawMsg(self, ascr, x, y, w, h, colBg=ansi.BLACK, ichl=0):
|
|
yDraw = y + h - 1
|
|
for chl in self.rgchl[ichl:]:
|
|
stUser = chl.StUser()
|
|
colFg = chl.Col()
|
|
wMsg = w - len(stUser) - 1
|
|
rgstMsg = RgstWrapped(chl.st, wMsg)
|
|
|
|
rgstMsg.reverse()
|
|
for istMsg, stMsg in enumerate(rgstMsg):
|
|
if istMsg == len(rgstMsg) - 1:
|
|
st = "{0} {1:{2}}".format(stUser, stMsg, wMsg)
|
|
else:
|
|
st = "{0:{1}} {2:{3}}".format("", len(stUser), stMsg, wMsg)
|
|
ascr.PutSt(st, x, yDraw, colFg, colBg)
|
|
yDraw = yDraw - 1
|
|
if yDraw == y - 1:
|
|
return
|
|
while yDraw >= y:
|
|
ascr.PutSt("{0:{1}}".format("", w), x, yDraw, ansi.WHITE, colBg)
|
|
yDraw = yDraw - 1
|
|
|
|
def DrawUsers(self, ascr, x, y, w, h, colBg=ansi.BLACK, iuser=0):
|
|
yDraw = y
|
|
for client in sorted(self.game.RgclientConnected(), lambda c1, c2: cmp(c1.Cldg(self.game).iclient, c2.Cldg(self.game).iclient))[iuser:]:
|
|
ascr.PutSt("{0:{1}}".format(str(client.Cldg(self.game).iclient) + ") " + client.cld.user, w), x, yDraw, ansi.WHITE, colBg)
|
|
yDraw = yDraw + 1
|
|
if yDraw == y + h:
|
|
break
|
|
while yDraw < y + h:
|
|
ascr.PutSt("{0:{1}}".format("", w), x, yDraw, ansi.WHITE, colBg)
|
|
yDraw = yDraw + 1
|
|
|
|
class MiChat(Mi):
|
|
KWNDMSG = 0
|
|
KWNDUSERS = 1
|
|
def InitPersistent(self, client, height, chat):
|
|
Mi.InitPersistent(self)
|
|
self.client = client
|
|
self.chat = chat
|
|
self.height = height
|
|
assert height > 2
|
|
self.kwnd = self.KWNDMSG
|
|
self.imsg = 0
|
|
self.iuser = 0
|
|
|
|
def StName(self):
|
|
return "Chat"
|
|
|
|
def HandleKey(self, key):
|
|
if self.kwnd == self.KWNDUSERS:
|
|
if key == ansi.K_LEFT:
|
|
self.kwnd = self.KWNDMSG
|
|
elif (key == 'a' or key == 'A') and self.iuser > 0:
|
|
self.iuser = self.iuser - 1
|
|
elif (key == 'z' or key == 'Z') and self.iuser < self.chat.CUsers() - 1:
|
|
self.iuser = self.iuser + 1
|
|
else:
|
|
return False
|
|
else: #KWNDMSG
|
|
if key == ansi.K_RIGHT:
|
|
self.kwnd = self.KWNDUSERS
|
|
elif (key == 'a' or key == 'A') and self.imsg < self.chat.CChl() - 1:
|
|
self.imsg = self.imsg + 1
|
|
elif (key == 'z' or key == 'Z') and self.imsg > 0:
|
|
self.imsg = self.imsg - 1
|
|
else:
|
|
return False
|
|
return True
|
|
|
|
def Height(self, w):
|
|
return self.height
|
|
|
|
def ColBgDefault(self):
|
|
return ansi.BLACK
|
|
def ColBgSelected(self, fPri):
|
|
return ansi.BLACK
|
|
def ColFg(self):
|
|
return ansi.WHITE
|
|
|
|
def FIgnoreParent(self):
|
|
return True
|
|
|
|
def Draw(self, ascr, x, y, w, selor):
|
|
wChat = (w / 4) * 3
|
|
wUser = (w / 4) - 1
|
|
self.chat.DrawMsg(ascr, x, y, wChat, self.height - 1, self.ColBg(selor), self.imsg)
|
|
for yT in range(y, y + self.height - 1):
|
|
ascr.PutAch(ansi.MkAch(chr(179)), x + wChat, yT)
|
|
ascr.PutSt("{0:{1}<{2}}{3}{0:{1}<{4}}".format("", chr(196), wChat, chr(193), wUser), x, y + self.height - 1)
|
|
self.chat.DrawUsers(ascr, x + wChat + 1, y, wUser, self.height - 1, self.ColBg(selor), self.iuser)
|
|
if selor.KSel() == Selor.PRI:
|
|
if self.kwnd == self.KWNDMSG:
|
|
xBar = x + wChat - 1
|
|
pct = 1 - (self.imsg / float(self.chat.CChl()))
|
|
else: # KWNDUSERS
|
|
xBar = x + w - 1
|
|
pct = self.iuser / float(self.chat.CUsers())
|
|
yBar = int(y + (pct * (self.height - 4)))
|
|
ascr.PutAch(ansi.MkAch("A", ansi.RED, ansi.WHITE), xBar, yBar)
|
|
ascr.PutAch(ansi.MkAch(chr(186), ansi.RED, ansi.WHITE), xBar, yBar + 1)
|
|
ascr.PutAch(ansi.MkAch("Z", ansi.RED, ansi.WHITE), xBar, yBar + 2)
|
|
|
|
|
|
|
|
class Joiner(LeaveJoinToken):
|
|
def InitPersistent(self, owner):
|
|
Token.InitPersistent(self, owner)
|
|
self.msgScroller = self.game.rgtoken("msgscroller")[0]
|
|
|
|
def OnJoin(self, client):
|
|
with client.Cldg(self.game).SetTransiently() as cldg:
|
|
cldg.iclient = -1
|
|
iclient = 1
|
|
rgiclient = [clientT.Cldg(self.game).iclient for clientT in self.game.RgclientConnected()]
|
|
while iclient in rgiclient:
|
|
iclient = iclient + 1
|
|
cldg.iclient = iclient
|
|
self.msgScroller.evPost.fire(client.cld.user + "(#" + str(cldg.iclient) + ") has joined! :)")
|
|
self.game.Chat().Add(ChlSys(client, "has joined"))
|
|
|
|
def OnLeave(self, client):
|
|
self.msgScroller.evPost.fire(client.cld.user + " has left! :(")
|
|
self.game.Chat().Add(ChlSys(client, "has left"))
|
|
|
|
## TODO:
|
|
## - fix lobby numbering (clients either editing or playing a game)
|
|
|
|
class Player(TokenClient):
|
|
def InitPersistent(self, owner, client):
|
|
TokenClient.InitPersistent(self, owner, client, "drawable", "player")
|
|
self.rbot = self.game.vm.AddPlayer()
|
|
self.vm = self.game.vm
|
|
chat = self.game.chat
|
|
miQuit = MiButton("Leave Game", self.client.leaveGame)
|
|
self.mi = MiMenu([None, MiChat(self.client, 13, chat), MiTypein(Partial(chat.SendMsg, self.client)), None, miQuit, None])
|
|
self.ovPopup = OvPopup(self, self.client, self.mi)
|
|
|
|
def die(self):
|
|
self.vm.RemovePlayer(self.rbot)
|
|
TokenClient.die(self)
|
|
|
|
def run(self):
|
|
while True:
|
|
key = self.EvKey().receive(self)
|
|
posNew = self.rbot.Pos().Clone()
|
|
if key == ansi.K_UP:
|
|
posNew.Up()
|
|
elif key == ansi.K_DOWN:
|
|
posNew.Down()
|
|
elif key == ansi.K_LEFT:
|
|
posNew.Left()
|
|
elif key == ansi.K_RIGHT:
|
|
posNew.Right()
|
|
elif key == ansi.K_TAB:
|
|
self.ovPopup.evShow.fire()
|
|
self.ovPopup.evDone.receive(self)
|
|
if not posNew.Equals(self.rbot.Pos()):
|
|
# is there an rbot here?
|
|
rgrbotCollide = [rbot for rbot in self.vm.Rgrbot() if rbot != self.rbot and rbot.Pos().Equals(posNew)]
|
|
if len(rgrbotCollide) > 0:
|
|
try:
|
|
rgrbotCollide[0].botdef.syntOnTouch.Eval(self)
|
|
except:
|
|
traceback.print_exc()
|
|
elif self.game.board.AchAtPos(posNew) == ansi.achBlank:
|
|
self.rbot.Move(posNew)
|
|
|
|
def draw(self, ascr, client):
|
|
if client == self.client:
|
|
self.game.board.draw(ascr)
|
|
for rbot in self.vm.Rgrbot():
|
|
ascr.PutAch(rbot.Ach(), rbot.Pos().X(), rbot.Pos().Y())
|
|
|
|
def GlobalMsg(self, stMsg):
|
|
self.game.rgtoken("msgscroller")[0].evPost.fire(stMsg)
|
|
|
|
# idea: synchronous event pair to implement "show message" w/ blocking
|
|
|
|
class GameWorld(Game):
|
|
def InitPersistent(self, board):
|
|
Game.InitPersistent(self)
|
|
self.board = board
|
|
self.vm = Vm(board.defs)
|
|
self.dtStart = datetime.now()
|
|
self.chat = Chat()
|
|
|
|
def InitTransient(self):
|
|
Game.InitTransient(self)
|
|
if not self.FDead():
|
|
self.chat.AttachToGame(self)
|
|
self.rgdgdie.append(self.chat.DetachFromGame)
|
|
|
|
def Chat(self):
|
|
return self.chat
|
|
|
|
def StName(self):
|
|
return self.dtStart.strftime("%b %d, %Y at %I:%M%p") + " [" + str(len(self.RgclientConnected())) + "]"
|
|
|
|
def GetRgclsTokTrans(self):
|
|
return [[AutoJoiner, Player], MsgScroller, Joiner, AutoKiller]
|
|
|
|
def GetRgtagDraw(self):
|
|
return ["player", "overlay"]
|