def unbindkp(self, update: Update, context: CallbackContext) -> bool:
        """撤销自己的KP权限。只有当前群内KP可以使用该指令。
        在撤销KP之后的新KP会自动获取原KP的所有NPC的卡片"""

        if isprivate(update):
            return self.errorInfo('发群消息撤销自己的KP权限')

        gp = self.forcegetgroup(update)
        if gp.kp is None:
            return self.errorInfo('本群没有KP', True)

        if not self.checkaccess(self.forcegetplayer(update), gp) & GROUPKP:
            return self.errorInfo('你不是KP', True)

        self.changecardsplid(gp, gp.kp, self.forcegetplayer(0))

        kp = gp.kp
        self.delkp(gp)

        kp.write()
        gp.write()

        self.reply('KP已撤销')

        if self.getOP(gp.id).find("delcard") != -1:
            self.popOP(gp.id)

        return True
    def showjoblist(self, update: Update, context: CallbackContext) -> None:
        """显示职业列表"""

        if not isprivate(update):
            self.errorInfo("请在私聊中使用该指令")
            return

        rttext = "职业列表:"
        counts = 0

        for job in self.joblist:
            jobinfo = self.joblist[job]

            rttext += job + f":\n信用范围 [{str(jobinfo[0])},{str(jobinfo[1])}]\n"

            rttext += "技能点计算方法:"
            calcd: Dict[str, int] = jobinfo[2]
            calcmeth = " 加 ".join("或".join(x.split('_')) + "乘" + str(calcd[x])
                                  for x in calcd)
            rttext += calcmeth + "\n"

            rttext += "主要技能:" + "、".join(x for x in jobinfo[3:]) + "\n"

            counts += 1

            if counts == 3:
                self.reply(rttext)
                rttext = ""
                counts = 0
                time.sleep(0.2)
Beispiel #3
0
    def startgame(self, update: Update, context: CallbackContext) -> bool:
        """开始一场游戏。

        这一指令将拷贝本群内所有卡,之后将用拷贝的卡片副本进行游戏,修改属性将不会影响到游戏外的原卡属性。
        如果要正常结束游戏,使用`/endgame`可以将游戏的角色卡数据覆写到原本的数据上。
        如果要放弃这些游戏内进行的修改,使用`/abortgame`会直接删除这些副本。
        `/startgame`:正常地开始游戏,对所有玩家的卡片(type为PL)进行卡片检查。
        `/startgame ignore`跳过开始游戏的检查,直接开始游戏。

        开始后,bot会询问是否保存聊天文本数据。此时回复cancel或者取消,即可取消开始游戏。"""

        if isprivate(update):
            return self.errorInfo("游戏需要在群里进行")

        gp = self.forcegetgroup(update)
        kp = self.forcegetplayer(update)
        if gp.kp != kp:
            return self.errorInfo("游戏只能由KP发起", True)
        if gp.game is not None:
            return self.errorInfo("游戏已经在进行中")

        if gp.pausedgame is not None:
            return self.continuegame(update, context)  # 检测到游戏暂停中,直接继续

        # 开始验证
        if not(len(context.args) > 0 and context.args[0] == "ignore"):
            if not self.checkstart(gp):
                return False

        self.reply(
            "准备开始游戏,是否需要记录聊天文本?如果需要记录文本,请回复'记录',否则不会记录文本。\n\
            回复'cancel'或者'取消'来取消游戏。"
        )
        self.addOP(gp.id, "startgame")
        return True
    def showrule(self, update: Update, context: CallbackContext) -> bool:
        """显示当前群内的规则。
        如果想了解群规则的详情,请查阅setrule指令的帮助:
        `/help setrule`"""

        if isprivate(update):
            return self.errorInfo("请在群内查看规则")

        gp = self.forcegetgroup(update)
        rule = gp.rule

        self.reply(str(rule))
        return True
    def transferkp(self, update: Update, context: CallbackContext) -> bool:
        """转移KP权限,只有群管理员可以使用这个指令。
        当前群没有KP时或当前群KP为管理员时,无法使用。

        `/transferkp --kpid`:将当前群KP权限转移到某个群成员。
        如果指定的`kpid`不在群内则无法设定。

        `/transferkp`:将当前群KP权限转移到自身。

        `/trasferkp`(reply to someone):将kp权限转移给被回复者。"""
        if isprivate(update):
            return self.errorInfo("发送群消息强制转移KP权限")

        gp = self.getgp(update)
        pl = self.getplayer(update)
        f = self.checkaccess(pl, gp)

        if not f & GROUPADMIN:
            return self.errorInfo("没有权限", True)

        if gp.kp is None:
            return self.errorInfo("没有KP", True)

        if self.checkaccess(gp.kp, gp) & GROUPADMIN:
            return self.errorInfo("KP是管理员,无法转移")

        # 获取newkp
        newkpid: int
        if len(context.args) != 0:
            if not isint(context.args[0]):
                return self.errorInfo("参数需要是整数", True)
            newkp = self.forcegetplayer(int(context.args[0]))
        else:
            t = self.getreplyplayer(update)
            newkp = t if t is not None else self.forcegetplayer(update)

        if newkp == gp.kp:
            return self.errorInfo("原KP和新KP相同", True)

        if not self.changeKP(gp, newkp):
            return self.errorInfo("程序错误:不符合添加KP要求,请检查代码")  # 不应触发

        return True
Beispiel #6
0
    def setname(self, update: Update, context: CallbackContext) -> bool:
        """设置角色卡姓名。

        `/setname --name`:直接设定姓名。
        `/setname`:bot将等待输入姓名。
        设置的姓名可以带有空格等字符。"""

        card1 = self.forcegetplayer(update).controlling
        if card1 is None:
            return self.errorInfo("找不到卡。")

        if len(context.args) == 0:
            if isprivate(update):
                self.addOP(getchatid(update), "setname")
            else:
                self.addOP(getchatid(update), "setname " + str(card1.playerid))
            self.reply("请输入姓名:")
            return True

        self.nameset(card1, ' '.join(context.args))
        self.reply("角色的名字已经设置为" + card1.info.name + "。")
        return True
    def bindkp(self, update: Update, context: CallbackContext) -> bool:
        """添加KP。在群里发送`/bindkp`将自己设置为KP。
        如果这个群已经有一名群成员是KP,则该指令无效。
        若原KP不在群里,该指令可以替换KP。

        如果原KP在群里,需要先发送`/unbindkp`来撤销自己的KP,或者管理员用`/transferkp`来强制转移KP权限。"""

        if isprivate(update):
            return self.errorInfo('发送群消息添加KP')

        gp = self.forcegetgroup(update)
        kp = self.forcegetplayer(update)

        # 判断是否已经有KP
        if gp.kp is not None:
            # 已有KP
            if not self.isingroup(gp, kp):
                if not self.changeKP(gp, kp):  # 更新NPC卡拥有者
                    # 不应触发
                    return self.errorInfo("程序错误:不符合添加KP要求,请检查代码")
                return True

            return self.errorInfo(
                "你已经是KP了", True
            ) if gp.kp == kp else self.errorInfo(
                '这个群已经有一位KP了,请先让TA发送 /unbindkp 撤销自己的KP。如果需要强制更换KP,请管理员用\'/transferkp kpid\'添加本群成员为KP,或者 /transferkp 将自己设为KP。'
            )

        # 该群没有KP,可以直接添加KP
        self.addkp(gp, kp)

        # delkp指令会将KP的卡playerid全部改为0,检查如果有id为0的卡,id设为新kp的id
        self.changecardsplid(gp, self.forcegetplayer(0), kp)

        self.reply("绑定群(id): " + gp.getname() + "与KP(id): " + kp.getname())

        return True
Beispiel #8
0
    def sancheck(self, update: Update, context: CallbackContext) -> bool:
        """进行一次sancheck,格式如下:
        `/sancheck checkpass/checkfail`"""

        if isprivate(update):
            return self.errorInfo("在游戏中才能进行sancheck。")

        if len(context.args) == 0:
            return self.errorInfo("需要参数", True)

        checkname = context.args[0]
        if checkname.find("/") == -1:
            return self.errorInfo("将成功和失败的扣除点数用/分开。")

        checkpass, checkfail = checkname.split(sep='/', maxsplit=1)
        if not isadicename(checkpass) or not isadicename(checkfail):
            return self.errorInfo("无效输入")

        gp = self.forcegetgroup(update)

        if gp.game is None:
            return self.errorInfo("找不到游戏", True)

        pl = self.forcegetplayer(update)
        # KP 进行
        if pl == gp.kp:
            card1 = gp.game.kpctrl
            if card1 is None:
                return self.errorInfo("请先用 /switchgamecard 切换到你的卡")
        else:  # 玩家进行
            card1 = self.findcardfromgame(gp.game, pl)
            if card1 is None:
                return self.errorInfo("找不到卡。")

        rttext = "理智:检定/出目 "
        sanity = card1.attr.SAN
        check = dicemdn(1, 100)[0]
        rttext += str(sanity) + "/" + str(check) + " "
        greatfailrule = gp.rule.greatfail
        if (sanity < 50 and check >= greatfailrule[2]
                and check <= greatfailrule[3]) or (
                    sanity >= 50 and check >= greatfailrule[0]
                    and check <= greatfailrule[1]):  # 大失败
            rttext += "大失败"
            anstype = "大失败"
        elif check > sanity:  # check fail
            rttext += "失败"
            anstype = "失败"
        else:
            rttext += "成功"
            anstype = ""

        rttext += "\n损失理智:"
        sanloss, m, n = 0, 0, 0

        if anstype == "大失败":
            if isint(checkfail):
                sanloss = int(checkfail)
            else:
                t = checkfail.split("+")
                for tt in t:
                    if isint(tt):
                        sanloss += int(tt)
                    else:
                        ttt = tt.split('d')
                        sanloss += int(ttt[0]) * int(ttt[1])

        elif anstype == "失败":
            if isint(checkfail):
                sanloss = int(checkfail)
            else:
                m, n = checkfail.split("d", maxsplit=1)
                m, n = int(m), int(n)
                sanloss = int(sum(dicemdn(m, n)))

        else:
            if isint(checkpass):
                sanloss = int(checkpass)
            else:
                m, n = checkpass.split("d", maxsplit=1)
                m, n = int(m), int(n)
                sanloss = int(sum(dicemdn(m, n)))

        card1.attr.SAN -= sanloss
        rttext += str(sanloss) + "\n"
        if card1.attr.SAN <= 0:
            card1.attr.SAN = 0
            card1.status = STATUS_PERMANENTINSANE
            rttext += "陷入永久疯狂,快乐撕卡~\n"

        elif sanloss > (card1.attr.SAN + sanloss) // 5:
            rttext += "一次损失五分之一以上理智,进入不定性疯狂状态。\n"
            # TODO 处理角色的疯狂状态
        elif sanloss >= 5:
            rttext += "一次损失5点或以上理智,可能需要进行智力(灵感)检定。\n"

        self.reply(rttext)
        card1.write()
        return True
Beispiel #9
0
    def roll(self, update: Update, context: CallbackContext):
        """基本的骰子功能。

        只接受第一个空格前的参数`dicename`。
        `dicename`可能是技能名、属性名(仅限游戏中),可能是`3d6`,可能是`1d4+2d10`。
        骰子环境可能是游戏中,游戏外。

        `/roll`:默认1d100。
        `/roll --mdn`骰一个mdn的骰子。
        `/roll --test`仅限游戏中可以使用。对`test`进行一次检定。
        例如,`/roll 力量`会进行一次STR检定。
        `/roll 射击`进行一次射击检定。
        检定心理学时结果只会发送给kp。
        如果要进行一个暗骰,可以输入
        `/roll 暗骰`进行一次检定为50的暗骰,或者
        `/roll 暗骰60`进行一次检定为60的暗骰。"""

        if len(context.args) == 0:
            self.reply(commondice("1d100"))  # 骰1d100
            return True

        dicename = context.args[0]

        if isprivate(update):
            self.reply(commondice(dicename))
            return True

        gp = self.forcegetgroup(update)

        # 检查输入参数是不是一个基础骰子,如果是则直接计算骰子
        if gp.game is None or dicename.find('d') >= 0 or isint(dicename):
            if isint(dicename) and int(dicename) > 0:
                dicename = "1d" + dicename
            rttext = commondice(dicename)
            if rttext == "Invalid input.":
                return self.errorInfo("输入无效")
            self.reply(rttext)
            return True

        if gp.game is None:
            return self.errorInfo("输入无效")
        # 确认不是基础骰子的计算,转到卡检定
        # 获取临时检定
        tpcheck, gp.game.tpcheck = gp.game.tpcheck, 0
        if tpcheck != 0:
            gp.write()

        pl = self.forcegetplayer(update)

        # 获取卡
        if pl != gp.kp:
            gamecard = self.findcardfromgame(gp.game, pl)
        else:
            gamecard = gp.game.kpctrl
            if gamecard is None:
                return self.errorInfo("请用 /switchgamecard 切换kp要用的卡")
        if not gamecard:
            return self.errorInfo("找不到游戏中的卡。")
        if gamecard.status == STATUS_DEAD:
            return self.errorInfo("角色已死亡")
        if gamecard.status == STATUS_PERMANENTINSANE:
            return self.errorInfo("角色已永久疯狂")

        if dicename.encode('utf-8').isalpha():
            dicename = dicename.upper()

        # 找卡完成,开始检定
        test = 0
        if dicename == "侦察":
            dicename = "侦查"
        if dicename in gamecard.skill.allskills():
            test = gamecard.skill.get(dicename)
        elif dicename in gamecard.interest.allskills():
            test = gamecard.interest.get(dicename)
        elif dicename == "母语":
            test = gamecard.data.EDU
        elif dicename == "闪避":
            test = gamecard.data.DEX // 2

        elif dicename in gamecard.data.alldatanames:
            test = gamecard.data.__dict__[dicename]
        elif dicename == "力量":
            dicename = "STR"
            test = gamecard.data.STR
        elif dicename == "体质":
            dicename = "CON"
            test = gamecard.data.CON
        elif dicename == "体型":
            dicename = "SIZ"
            test = gamecard.data.SIZ
        elif dicename == "敏捷":
            dicename = "DEX"
            test = gamecard.data.DEX
        elif dicename == "外貌":
            dicename = "APP"
            test = gamecard.data.APP
        elif dicename == "智力" or dicename == "灵感":
            dicename = "INT"
            test = gamecard.data.INT
        elif dicename == "意志":
            dicename = "POW"
            test = gamecard.data.POW
        elif dicename == "教育":
            dicename = "EDU"
            test = gamecard.data.EDU
        elif dicename == "幸运":
            dicename = "LUCK"
            test = gamecard.data.LUCK

        elif dicename in self.skilllist:
            test = self.skilllist[dicename]

        elif dicename[:2] == "暗骰" and (isint(dicename[2:])
                                       or len(dicename) == 2):
            if len(dicename) != 2:
                test = int(dicename[2:])
            else:
                test = 50

        else:  # HIT BAD TRAP
            return self.errorInfo("输入无效")

        # 将所有检定修正相加
        test += gamecard.tempstatus.GLOBAL
        if gamecard.hasstatus(dicename):
            test += gamecard.getstatus(dicename)
        test += tpcheck

        if test < 1:
            test = 1
        testval = dicemdn(1, 100)[0]
        rttext = dicename + " 检定/出目:" + str(test) + "/" + str(testval) + " "

        greatsuccessrule = gp.rule.greatsuccess
        greatfailrule = gp.rule.greatfail

        if (test < 50 and testval >= greatfailrule[2]
                and testval <= greatfailrule[3]) or (
                    test >= 50 and testval >= greatfailrule[0]
                    and testval <= greatfailrule[1]):
            rttext += "大失败"
        elif (test < 50 and testval >= greatsuccessrule[2]
              and testval <= greatsuccessrule[3]) or (
                  test >= 50 and testval >= greatsuccessrule[0]
                  and testval <= greatsuccessrule[1]):
            rttext += "大成功"
        elif testval > test:
            rttext += "失败"
        elif testval > test // 2:
            rttext += "普通成功"
        elif testval > test // 5:
            rttext += "困难成功"
        else:
            rttext += "极难成功"

        if dicename == "心理学" or dicename[:2] == "暗骰":
            if gp.kp is None:
                return self.errorInfo("本群没有KP,请先添加一个KP再试!")

            self.reply(dicename + " 检定/出目:" + str(test) + "/???")
            self.sendto(gp.kp, rttext)
        else:
            self.reply(rttext)

        return True
Beispiel #10
0
    def showcard(self, update: Update, context: CallbackContext) -> bool:
        """显示某张卡的信息。

        `/showcard --cardid (card/--attrname)`: 显示卡id为`cardid`的卡片的信息。
        如果第二个参数是`card`,显示整张卡;否则,显示这一项数据。
        如果第二个参数不存在,显示卡片基本信息。
        群聊时使用该指令,优先查看游戏内的卡片。

        显示前会检查发送者是否有权限显示这张卡。在这些情况下,无法显示卡:

        群聊环境:显示非本群的卡片,或者显示本群的type不为PL的卡片;

        私聊环境:显示没有查看权限的卡片。"""

        if len(context.args) == 0:
            return self.errorInfo("需要参数")
        if not isint(context.args[0]) or int(context.args[0]) < 0:
            return self.errorInfo("卡id参数无效", True)
        cdid = int(context.args[0])

        rttext: str = ""
        cardi: Optional[GameCard] = None

        if isgroup(update):
            cardi = self.getgamecard(cdid)
            if cardi is not None:
                rttext = "显示游戏内的卡片\n"

        if cardi is None:
            cardi = self.getcard(cdid)

            if cardi is None:
                return self.errorInfo("找不到这张卡")

        if rttext == "":
            rttext = "显示游戏外的卡片\n"

        # 检查是否有权限
        if isprivate(update):

            pl = self.forcegetplayer(update)

            if self.checkaccess(pl, cardi) & CANREAD == 0:
                return self.errorInfo("没有权限")
        else:
            if (cardi.groupid != -1 and cardi.group !=
                    self.forcegetgroup(update)) or cardi.type != PLTYPE:
                return self.errorInfo("没有权限", True)

        # 开始处理
        if len(context.args) >= 2:
            if context.args[1] == "card":
                self.reply(rttext + str(cardi))
            else:
                ans = cardi.show(context.args[1])
                if ans == "找不到该属性":
                    return self.errorInfo(ans)

                self.reply(rttext + ans)
            return True

        # 显示基本属性
        self.reply(rttext + cardi.basicinfo())
        return True
Beispiel #11
0
    def newcard(self, update: Update, context: CallbackContext) -> bool:
        """随机生成一张新的角色卡。需要一个群id作为参数。
        只接受私聊消息。

        如果发送者不是KP,那么只能在一个群内拥有最多一张角色卡。

        如果不知道群id,请先发送`/getid`到群里获取id。

        `/newcard`提交创建卡请求,bot会等待你输入`groupid`。
        `/newcard --groupid`新建一张卡片,绑定到`groupid`对应的群。
        `/newcard --cardid`新建一张卡片,将卡片id设置为`cardid`,`cardid`必须是非负整数。
        `/newcard --groupid --cardid`新建一张卡片,绑定到`groupid`对应的群的同时,将卡片id设置为`cardid`。

        当指定的卡id已经被别的卡占用的时候,将自动获取未被占用的id。

        当生成时有至少三项基础属性低于50时,可以使用`/discard`来放弃并删除这张角色卡。
        创建新卡之后,当前控制卡片会自动切换到新卡,详情参见
        `/help switch`。

        角色卡说明
        一张角色卡具有:
        `groupid`,`id`,`playerid`基本信息。
        STR,CON,SIZ,DEX,APP,INT,EDU,LUCK基本属性;
        职业、姓名、性别、年龄;
        技能信息;
        背景故事(描述,重要之人,重要之地,珍视之物,特质,受过的伤,恐惧之物,神秘学物品,第三类接触);
        检定修正值;
        物品,财产;
        角色类型(PL,NPC);
        是否可以被删除;
        状态(存活,死亡,疯狂等)。"""

        gpid: int = None
        gp: Optional[Group] = None
        newcdid: Optional[int] = None

        if isgroup(update):
            # 先检查是否有该玩家信息
            rtbutton = [[InlineKeyboardButton(
                text="跳转到私聊", callback_data="None", url="t.me/"+self.bot.username)]]
            rp_markup = InlineKeyboardMarkup(rtbutton)
            if self.getplayer(update) is None:
                self.reply("请先开启与bot的私聊", reply_markup=rp_markup)
                return True

            if len(context.args) > 0:
                if not isint(context.args[0]) or int(context.args[0]) < 0:
                    return self.errorInfo("参数无效")

            gpid = getchatid(update)
            gp = self.forcegetgroup(gpid)
            if len(context.args) > 0:
                newcdid = int(context.args[0])

        elif len(context.args) > 0:
            msg = context.args[0]

            if not isint(msg):
                return self.errorInfo("输入无效")

            if int(msg) >= 0:
                newcdid = int(msg)
            else:
                gpid = int(msg)
                gp = self.forcegetgroup(gpid)
                if len(context.args) > 1:
                    if not isint(context.args[1]) or int(context.args[1]) < 0:
                        return self.errorInfo("输入无效")
                    newcdid = int(context.args[1])

        if gp is None:
            self.reply(
                "准备创建新卡。\n如果你不知道群id,在群里发送 /getid 即可创建角色卡。\n你也可以选择手动输入群id,请发送群id:")
            if newcdid is None:
                self.addOP(getchatid(update), "newcard " +
                           str(update.message.message_id))
            else:
                self.addOP(getchatid(update), "newcard " +
                           str(update.message.message_id)+" "+str(newcdid))
            return True

        # 检查(pl)是否已经有卡
        pl = self.forcegetplayer(update)
        plid = pl.id
        if self.hascard(plid, gpid) and pl != gp.kp:
            return self.errorInfo("你在这个群已经有一张卡了!")

        # 符合建卡条件,生成新卡
        # gp is not None
        assert(gpid is not None)

        remsgid = None
        if isprivate(update):
            remsgid = update.message.message_id
        else:
            assert rp_markup
            self.reply("建卡信息已经私聊发送", reply_markup=rp_markup)

        return self.getnewcard(remsgid, gpid, plid, newcdid)
    def setrule(self, update: Update, context: CallbackContext) -> bool:
        """设置游戏的规则。
        一个群里游戏有自动生成的默认规则,使用本指令可以修改这些规则。

        `/setrule --args`修改规则。`--args`格式如下:

        `rulename1:str --rules1:List[int] rulename2:str --rule2:List[int] ...`

        一次可以修改多项规则。
        有可能会出现部分规则设置成功,但部分规则设置失败的情况,
        查看返回的信息可以知道哪些部分已经成功修改。

        规则的详细说明:

        skillmax:接收长度为3的数组,记为r。`r[0]`是一般技能上限,
        `r[1]`是个别技能的上限,`r[2]`表示个别技能的个数。

        skillmaxAged:年龄得到的技能上限增加设定。
        接收长度为4的数组,记为r。`r[0]`至`r[2]`同上,
        但仅仅在年龄大于`r[3]`时开启该设定。`r[3]`等于100代表不开启该设定。

        skillcost:技能点数分配时的消耗。接收长度为偶数的数组,记为r。
        若i为偶数(或0),`r[i]`表示技能点小于`r[i+1]`时,
        需要分配`r[i]`点点数来获得1点技能点。r的最后一项必须是100。
        例如:`r=[1, 80, 2, 100]`,则从10点升至90点需要花费`1*70+2*10=90`点数。

        greatsuccess:大成功范围。接收长度为4的数组,记为r。
        `r[0]-r[1]`为检定大于等于50时大成功范围,否则是`r[2]-r[3]`。

        greatfail:大失败范围。同上。"""

        if isprivate(update):
            return self.errorInfo("请在群内用该指令设置规则")

        gp = self.forcegetgroup(update)

        if not self.isfromkp(update):
            return self.errorInfo("没有权限", True)

        if len(context.args) == 0:
            return self.errorInfo("需要参数", True)

        gprule = gp.rule

        ruledict: Dict[str, List[int]] = {}

        i = 0
        while i < len(context.args):
            j = i+1
            tplist: List[int] = []
            while j < len(context.args):
                if isint(context.args[j]):
                    tplist.append(int(context.args[j]))
                    j += 1
                else:
                    break
            ruledict[context.args[i]] = tplist
            i = j
        del i, j

        msg, ok = gprule.changeRules(ruledict)
        if not ok:
            return self.errorInfo(msg)

        self.reply(msg)
        return True
    def hp(self, update: Update, context: CallbackContext) -> bool:
        """修改HP。KP通过回复某位PL消息并在回复消息中使用本指令即可修改对方卡片的HP。
        回复自己的消息,则修改kp当前选中的游戏卡。
        或者,也可以使用@用户名以及用玩家id的方法选中某名PL,但请不要同时使用回复和用户名。
        使用范例:
        `/hp +1d3`:恢复1d3点HP。
        `/hp -2`:扣除2点HP。
        `/hp 10`:将HP设置为10。
        `/hp @username 12`:将用户名为username的玩家HP设为12。
        下面的例子是无效输入:
        `/hp 1d3`:无法将HP设置为一个骰子的结果,恢复1d3生命请在参数前加上符号`+`,扣除同理。
        在生命变动的情况下,角色状态也会同步地变动。"""

        if isprivate(update):
            return self.errorInfo("游戏中才可以修改HP。")
        gp = self.forcegetgroup(update)
        kp = self.forcegetplayer(update)
        if gp.kp != kp:
            return self.errorInfo("没有权限", True)

        if len(context.args) == 0:
            return self.errorInfo("需要指定扣除的HP", True)

        chp: str = context.args[0]
        game = gp.game
        if game is None:
            return self.errorInfo("找不到进行中的游戏", True)

        rppl = self.getreplyplayer(update)
        if update.message.reply_to_message is not None:
            rpmsgid = update.message.reply_to_message.message_id
        else:
            rpmsgid = update.message.message_id

        if rppl is None:
            if len(context.args) < 2:
                return self.errorInfo("请用回复或@用户名的方式来选择玩家改变HP")
            if not isint(context.args[0]) or int(context.args[0]) < 0:
                return self.errorInfo("参数无效")
            rppl = self.getplayer(int(context.args[0]))
            if rppl is None:
                return self.errorInfo("指定的用户无效")
            chp = context.args[1]

        if rppl != kp:
            cardi = self.findcardfromgame(game, rppl)
        else:
            cardi = game.kpctrl

        if cardi is None:
            return self.errorInfo("找不到这名玩家的卡。")

        if chp[0] == "+" or chp[0] == "-":
            if len(chp) == 1:
                return self.errorInfo("参数无效", True)

            # 由dicecalculator()处理。减法时,检查可能的括号导致的输入错误
            if chp[0] == '-' and chp[1] != '(' and (chp[1:].find('+') != -1 or chp[1:].find('-') != -1):
                return self.errorInfo("当第一个减号的后面是可计算的骰子,且存在加减法时,请在第一个符号之后使用括号")

            try:
                diceans = dicecalculator(chp[1:])
            except Exception:
                return self.errorInfo("参数无效", True)

            if diceans < 0:
                return self.errorInfo("骰子的结果为0,生命值不修改")

            chp = chp[0]+str(diceans)
        else:
            # 直接修改生命为目标值的情形。不支持dicecalculator(),仅支持整数
            if not isint(chp) or int(chp) > 100 or int(chp) < 0:
                return self.errorInfo("参数无效", True)

        if cardi.status == STATUS_DEAD:
            return self.errorInfo("该角色已死亡")

        originhp = cardi.attr.HP
        if chp[0] == "+":
            cardi.attr.HP += int(chp[1:])
        elif chp[0] == "-":
            cardi.attr.HP -= int(chp[1:])
        else:
            cardi.attr.HP = int(chp)

        hpdiff = cardi.attr.HP - originhp
        if hpdiff == 0:
            return self.errorInfo("HP不变,目前HP:"+str(cardi.attr.HP))

        if hpdiff < 0:
            # 承受伤害描述。分类为三种状态
            takedmg = -hpdiff
            if takedmg < cardi.attr.MAXHP//2:
                # 轻伤,若生命不降到0,不做任何事
                if takedmg >= originhp:
                    self.reply(
                        text="HP归0,角色昏迷", reply_to_message_id=rpmsgid)
            elif takedmg > cardi.attr.MAXHP:
                self.reply(
                    text="致死性伤害,角色死亡", reply_to_message_id=rpmsgid)
                cardi.status = STATUS_DEAD
            else:
                self.reply(text="角色受到重伤,请进行体质检定以维持清醒",
                           reply_to_message_id=rpmsgid)
                cardi.status = STATUS_SERIOUSLYWOUNDED
                if originhp <= takedmg:
                    self.reply(
                        text="HP归0,进入濒死状态", reply_to_message_id=rpmsgid)
                    cardi.status = STATUS_NEARDEATH

            if cardi.attr.HP < 0:
                cardi.attr.HP = 0

        else:
            # 恢复生命,可能脱离某种状态
            if cardi.attr.HP >= cardi.attr.MAXHP:
                cardi.attr.HP = cardi.attr.MAXHP
                self.reply(text="HP达到最大值", reply_to_message_id=rpmsgid)

            if hpdiff > 1 and originhp <= 1 and cardi.status == STATUS_NEARDEATH:
                self.reply(text="脱离濒死状态", reply_to_message_id=rpmsgid)
                cardi.status = STATUS_SERIOUSLYWOUNDED
        cardi.write()

        self.reply(text="生命值从"+str(originhp)+"修改为" +
                   str(cardi.attr.HP), reply_to_message_id=rpmsgid)
        return True