From 388823edfe6308cba6f76ca6dc4f20022c5cb2be Mon Sep 17 00:00:00 2001
From: hxp <ale99527@vip.qq.com>
Date: 星期一, 30 六月 2025 19:03:50 +0800
Subject: [PATCH] 10431 【英文】看广告获得限时代金券

---
 ServerPython/CoreServerGroup/GameServer/Script/GameWorldLogic/AuctionHouse.py |  919 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 838 insertions(+), 81 deletions(-)

diff --git a/ServerPython/CoreServerGroup/GameServer/Script/GameWorldLogic/AuctionHouse.py b/ServerPython/CoreServerGroup/GameServer/Script/GameWorldLogic/AuctionHouse.py
index bdc0374..4b0ab3a 100644
--- a/ServerPython/CoreServerGroup/GameServer/Script/GameWorldLogic/AuctionHouse.py
+++ b/ServerPython/CoreServerGroup/GameServer/Script/GameWorldLogic/AuctionHouse.py
@@ -20,13 +20,19 @@
 import DataRecordPack
 import PyGameDataStruct
 import PlayerCompensation
+import IPY_PlayerDefine
 import ChPyNetSendPack
+import PlayerDBGSEvent
 import IpyGameDataPY
 import NetPackCommon
 import PlayerBourse
 import PlayerFamily
+import ShareDefine
+import PyGameData
 import ChConfig
 
+import random
+import datetime
 import operator
 import time
 import math
@@ -47,11 +53,24 @@
 AuctionRecordResult_BidOK, # 竞价成功
 AuctionRecordResult_BidFail, # 竞价失败
 AuctionRecordResult_MoveToWorld, # 仙盟拍品转移到全服拍品
-) = range(6)
+AuctionRecordResult_Unsell, # 下架
+) = range(7)
 
 # 当前拍品归类 0-全服拍品 1-仙盟私有拍品
 AuctionType_World = 0
 AuctionType_Family = 1
+
+'''
+竞价流程: 
+1. MapServer 先验证价格是否够
+2. GameServer 验证是否可以竞价
+3. MapServer 扣除玩家货币
+4. GameServer  进行竞价,变更下次竞价价格
+
+基于以上流程,所以玩家竞价时需先锁定物品,防止竞价流程未完结时其他玩家请求竞价同一拍品判断竞价价格错误
+锁定时长同样适用于拍品竞价时间结束时的保护时间
+'''
+BiddingQueryLockTick = 10000
 
 #拍卖行状态开关
 def GetAuctionHouseState(): return PlayerBourse.GetOpenState()
@@ -201,10 +220,22 @@
 
 def __InitAuctionAttentionAttrEx(attentionData):
     ## 初始化拍卖关注实例附加属性
-    setattr(attentionData, "AttentionItemIDList", [] if not attentionData.AttentionInfo else json.loads(attentionData.AttentionInfo))
+    setattr(attentionData, "AttentionItemIDList", [])
+    if attentionData.AttentionInfo.startswith("[") and attentionData.AttentionInfo.endswith("]"):
+        attentionData.AttentionItemIDList = json.loads(attentionData.AttentionInfo)
     return
 
 ##-------------------------------------------------------------------------------------------------
+def OnGameServerInitOK():
+    ## 服务器启动成功
+    
+    allAuctionItemByEndTimeList = PyDataManager.GetAuctionItemManager().allAuctionItemByEndTimeList
+    # 由于服务器未启动成功时取不到正确的开服天,所以启动成功后刷新一下拍品系统回购时间
+    for auctionItem in allAuctionItemByEndTimeList:
+        __SetAuctionItemSysBuyTime(auctionItem)
+        
+    __SortAuctionitem()
+    return
 
 def OnLoadAuctionItemDataEx(dbData):
     ## 加载拍卖物品表时附加处理
@@ -218,6 +249,7 @@
     
     if dbData.AuctionType == AuctionType_World:
         pyAuctionItemMgr.worldAuctionItemList.append(dbData)
+        __OnCalcWorldAuctionItemCount(dbData, 1)
         
     if familyID:
         familyItemList = pyAuctionItemMgr.familyAuctionItemDict.get(familyID, [])
@@ -254,9 +286,73 @@
     setattr(auctionItem, "BiddingQueryID", 0) # 当前正在竞价的玩家ID
     setattr(auctionItem, "BiddingQueryTick", 0) # 当前正在竞价的tick
     setattr(auctionItem, "EndTime", 0) # 结束竞价time值
+    setattr(auctionItem, "SysBuyTime", 0) # 系统一口价时间
     
+    __SetAuctionItemSysBuyTime(auctionItem)
     __SetAuctionItemEndTime(auctionItem, ipyData)
     return True
+
+def __SetAuctionItemSysBuyTime(auctionItem):
+    ## 更新系统一口价该拍品时间
+    if not GameWorld.GetGameWorld().GetDictByKey(ChConfig.Def_WorldKey_GameWorldInitOK):
+        #GameWorld.DebugLog("服务器未启动好,取不到正确的开服天,不处理拍品系统回购时间!")
+        return
+    #itemGUID = auctionItem.ItemGUID
+    itemID = auctionItem.ItemID
+    itemType = auctionItem.ItemType
+    playerID = auctionItem.PlayerID
+    familyID = auctionItem.FamilyID
+    if not playerID and not familyID:
+        #GameWorld.DebugLog("该拍品为系统上架的拍品,不设置系统一口价时间! GUID=%s,itemID=%s" % (itemGUID, itemID))
+        return
+    
+    if familyID:
+        #GameWorld.DebugLog("该拍品为仙盟拍品,不设置系统一口价时间! GUID=%s,itemID=%s,familyID=%s" % (itemGUID, itemID, familyID))
+        return
+    
+    # 注意: 因为GameServer没传是否套装,所以暂时按策划的ID规则来处理,最后一位代表是否套装
+    if itemID % 10 != 0:
+        #GameWorld.DebugLog("该拍品为套装拍品,不设置系统一口价时间! itemID=%s" % (itemID))
+        return
+    
+    if auctionItem.BidderID:
+        #GameWorld.DebugLog("该拍品已经有人竞价,不设置系统一口价时间! itemID=%s,bidderID=%s" % (itemID, auctionItem.BidderID))
+        return
+    
+    classLV = auctionItem.ItemClassLV
+    classSysBuyRateDict = IpyGameDataPY.GetFuncEvalCfg("AuctionItemSystem", 5)
+    if classLV in classSysBuyRateDict:
+        sysBuyRate = classSysBuyRateDict[classLV]
+        if not GameWorld.CanHappen(sysBuyRate, 100):
+            #GameWorld.DebugLog("该拍品阶概率不回收,不设置系统一口价时间! itemID=%s,classLV=%s,sysBuyRate=%s" % (itemID, classLV, sysBuyRate))
+            return
+        
+    canSysBuyItemTypeList = IpyGameDataPY.GetFuncEvalCfg("AuctionItemSystem", 2)
+    if itemType not in canSysBuyItemTypeList:
+        #GameWorld.DebugLog("该拍品类型不可被系统一口价,不设置系统一口价时间! itemID=%s,itemType=%s" % (itemID, itemType))
+        return
+    
+    sysBuyTimeRange = {}
+    openServerDay = PlayerDBGSEvent.GetDBGSTrig_ByKey(PlayerDBGSEvent.Def_ServerDay) + 1
+    oscDaySysBuyTimeRangeDict = IpyGameDataPY.GetFuncEvalCfg("AuctionItemSystem", 1, {})
+    openServerDayList = oscDaySysBuyTimeRangeDict.keys()
+    openServerDayList.sort()
+    for oscDay in openServerDayList:
+        if openServerDay <= oscDay:
+            sysBuyTimeRange = oscDaySysBuyTimeRangeDict[oscDay]
+            break
+    if len(sysBuyTimeRange) != 2:
+        #GameWorld.DebugLog("该开服天没有配置系统中间商回购支持! openServerDay=%s, %s" % (openServerDay, oscDaySysBuyTimeRangeDict))
+        return
+    randMinutes = random.randint(sysBuyTimeRange[0], sysBuyTimeRange[1])
+    addTimeStr = auctionItem.AddTime
+    addTime = GameWorld.ChangeTimeStrToNum(addTimeStr)
+    auctionItem.SysBuyTime = addTime + randMinutes * 60
+    pyAuctionItemMgr = PyDataManager.GetAuctionItemManager()
+    pyAuctionItemMgr.sysBuyoutItemByTimeList.append(auctionItem)
+    #GameWorld.DebugLog("更新拍品系统一口价时间: GUID=%s,itemID=%s,addTime=%s(%s),openServerDay=%s,randMinutes=%s(%s),sysBuyTime=%s" 
+    #                   % (itemGUID, itemID, addTime, addTimeStr, openServerDay, randMinutes, sysBuyTimeRange, auctionItem.SysBuyTime))
+    return
 
 def __SetAuctionItemEndTime(auctionItem, ipyData):
     ## 更新拍品结束竞价time值
@@ -278,8 +374,9 @@
         return
     bidTime = GameWorld.ChangeTimeStrToNum(auctionItem.BiddingTime)
     endTime = bidTime + IpyGameDataPY.GetFuncCfg("AuctionHouse", 4)
-    if endTime < auctionItem.EndTime:
+    if endTime <= auctionItem.EndTime:
         return
+    GameWorld.DebugLog("拍品加时: EndTime=%s,updEndTime=%s" % (auctionItem.EndTime, endTime))
     auctionItem.EndTime = endTime
     return True
 
@@ -304,21 +401,306 @@
     ## 仙盟拍卖中的拍品列表
     return PyDataManager.GetAuctionItemManager().familyAuctionItemDict.get(familyID, [])
 
-def OnAuctionItemTimeProcess(curTime):
+def IsFamilyMemberBiddingAuctionItem(familyID, memberID):
+    ## 仙盟成员是否最高竞价拍品中
+    familyAuctionItemList = GetFamilyAuctionItemList(familyID)
+    for auctionItem in familyAuctionItemList:
+        if auctionItem.BidderID == memberID:
+            return True
+    return False
+
+def __OnCalcWorldAuctionItemCount(auctionItem, changeCount):
+    ## 世界拍品数量变更统计处理
+    
+    if auctionItem.AuctionType != AuctionType_World:
+        return
+    
+    auctionItemMgr = PyDataManager.GetAuctionItemManager()
+    itemID = auctionItem.ItemID
+    #GameWorld.DebugLog("世界拍品数量变更统计: itemID=%s,changeCount=%s" % (itemID, changeCount))
+    
+    itemIDStr = str(itemID)
+    classLV = auctionItem.ItemClassLV
+    jobLimit = auctionItem.ItemJobLimit
+    # 统计有职业限制的境界装备
+    if jobLimit and classLV and len(itemIDStr) == 7:
+        color = int(itemIDStr[3:4])
+        isSuit = int(itemIDStr[-1])
+        jobEquipKey = (jobLimit, classLV, color, isSuit) # 职业,阶,颜色,是否套装
+        befCount = auctionItemMgr.worldAuctionJobEquipCountDict.get(jobEquipKey, 0)
+        updCount = max(befCount + changeCount, 0)
+        auctionItemMgr.worldAuctionJobEquipCountDict[jobEquipKey] = updCount
+        #GameWorld.DebugLog("    职业境界装备数量变更: jobLimit=%s,classLV=%s,color=%s,isSuit=%s,befCount=%s,updCount=%s" 
+        #                   % (jobLimit, classLV, color, isSuit, befCount, updCount))
+    # 其他的直接用itemID统计
+    else:
+        befCount = auctionItemMgr.worldAuctionItemCountDict.get(itemID, 0)
+        updCount = max(befCount + changeCount, 0)
+        auctionItemMgr.worldAuctionItemCountDict[itemID] = updCount
+        #GameWorld.DebugLog("    物品ID数量变更: itemID=%s,befCount=%s,updCount=%s" % (itemID, befCount, updCount))
+        
+    return
+
+def __GetAuctionSystemItemInfo():
+    key = "AuctionSystemItem"
+    openServerDay = PlayerDBGSEvent.GetDBGSTrig_ByKey(PlayerDBGSEvent.Def_ServerDay) + 1
+    AuctionSystemItem = IpyGameDataPY.GetConfigEx(key)
+    reloadSign = openServerDay
+    if AuctionSystemItem and AuctionSystemItem[0] == reloadSign:
+        GameWorld.DebugLog("已经加载过本日系统拍品处理信息!openServerDay=%s" % openServerDay)
+        return AuctionSystemItem[1]
+    
+    # 因为后面的时间判断都是精确到分的,而处理此逻辑的时候可能不是0秒,所以这里的datetime取当前时间精确到分的
+    serverTime = GameWorld.GetServerTime()
+    curDateStr = "%d-%d-%d" % (serverTime.year, serverTime.month, serverTime.day)
+    curDateTimeStr = "%d-%d-%d %02d:%02d:00" % (serverTime.year, serverTime.month, serverTime.day, serverTime.hour, serverTime.minute)
+    curDateTime = datetime.datetime.strptime(curDateTimeStr, ChConfig.TYPE_Time_Format)
+    
+    GameWorld.Log("===== 加载本日系统拍品信息: %s,openServerDay=%s =====" % (curDateTime, openServerDay))
+    addSystemAuctionItemInfo = []
+    ipyDataMgr = IpyGameDataPY.IPY_Data()
+    for index in xrange(ipyDataMgr.GetAuctionSystemItemCount()):
+        ipyData = ipyDataMgr.GetAuctionSystemItemByIndex(index)
+        cfgID = ipyData.GetCfgID()
+        startDateStr = ipyData.GetStartDate()
+        endDateStr = ipyData.GetEndDate()
+        startTimeStr = ipyData.GetStartTime()
+        endTimeStr = ipyData.GetEndTime()
+        auctionCount = ipyData.GetAuctionCount()
+        randMinuteRange = ipyData.GetRandMinuteRange()
+        replenishAuctionCount = ipyData.GetReplenishAuctionCount()
+        
+        GameWorld.DebugLog("cfgID=%s,startDateStr=%s,endDateStr=%s,startTimeStr=%s,endTimeStr=%s,auctionCount=%s,randMinuteRange=%s,replenishAuctionCount=%s" 
+                           % (cfgID, startDateStr, endDateStr, startTimeStr, endTimeStr, auctionCount, randMinuteRange, replenishAuctionCount))
+        
+        if not startDateStr:
+            startDateStr = curDateStr
+        elif startDateStr.isdigit():
+            startServerDay = int(startDateStr)
+            openServerDateTime = curDateTime + datetime.timedelta(days=(startServerDay-openServerDay))
+            startDateStr = "%d-%d-%d" % (openServerDateTime.year, openServerDateTime.month, openServerDateTime.day)
+            
+        if not endDateStr:
+            endDateStr = curDateStr
+        elif endDateStr.isdigit():
+            endServerDay = int(endDateStr)
+            endServerDateTime = curDateTime + datetime.timedelta(days=(endServerDay-openServerDay))
+            endDateStr = "%d-%d-%d" % (endServerDateTime.year, endServerDateTime.month, endServerDateTime.day)
+            
+        startDate = datetime.datetime.strptime("%s 00:00:00" % (startDateStr), ChConfig.TYPE_Time_Format)
+        endDate = datetime.datetime.strptime("%s 23:59:00" % (endDateStr), ChConfig.TYPE_Time_Format)
+        if serverTime < startDate or serverTime > endDate:
+            GameWorld.DebugLog("    不在上架日期内,不处理!")
+            continue
+        
+        if not startTimeStr:
+            startTimeStr = "00:00"
+        if not endTimeStr:
+            endTimeStr = "23:59"
+        startDatetime = datetime.datetime.strptime("%s %s:00" % (curDateStr, startTimeStr), ChConfig.TYPE_Time_Format)
+        endDatetime = datetime.datetime.strptime("%s %s:00" % (curDateStr, endTimeStr), ChConfig.TYPE_Time_Format)
+        if serverTime >= endDatetime:
+            GameWorld.DebugLog("    已超过今日的上架时间段,不处理!")
+            continue
+        if serverTime > startDatetime:
+            startDatetime = curDateTime
+            
+        addAuctionItemDatetimeList = []
+        nextAddAuctionItemDatetime = startDatetime
+        for _ in xrange(auctionCount):
+            if len(randMinuteRange) == 2:
+                nextAddMinutes = random.randint(randMinuteRange[0], randMinuteRange[1])
+            elif len(randMinuteRange) == 1:
+                nextAddMinutes = randMinuteRange[0]
+            else:
+                continue
+            nextAddAuctionItemDatetime += datetime.timedelta(minutes=nextAddMinutes)
+            if nextAddAuctionItemDatetime > endDatetime:
+                break
+            GameWorld.DebugLog("    添加上架系统拍品时间: nextAddMinutes=%s %s" % (nextAddMinutes, nextAddAuctionItemDatetime))
+            addAuctionItemDatetimeList.append(nextAddAuctionItemDatetime)
+            
+        # 动态补充拍品模式
+        if replenishAuctionCount:
+            GameWorld.DebugLog("    添加动态补充系统拍品计划: %s" % replenishAuctionCount)
+        # 指定上架拍品模式
+        elif addAuctionItemDatetimeList:
+            GameWorld.DebugLog("    添加上架系统拍品时间计划: %s" % addAuctionItemDatetimeList)
+        else:
+            continue
+        addSystemAuctionItemInfo.append([cfgID, ipyData, startDatetime, endDatetime, addAuctionItemDatetimeList])
+        
+    AuctionSystemItem = IpyGameDataPY.SetConfigEx(key, [reloadSign, addSystemAuctionItemInfo])
+    GameWorld.Log("本日系统拍品信息加载完毕!reloadSign=%s" % (reloadSign))
+    GameWorld.Log("本日系统拍品上架时间信息: %s" % addSystemAuctionItemInfo)
+    GameWorld.Log("=============================================================")
+    return AuctionSystemItem[1]
+
+def OnAuctionItemMinuteProcess(tick):
+    ## 拍卖行拍品定时处理,每整分钟触发一次
+    
+    # 这里时间需精确到分钟,不然后面的比较会匹配不到
+    curDateTime = GameWorld.GetServerTime()
+    curDateTime = datetime.datetime.strptime("%d-%d-%d %d:%d:00" % (curDateTime.year, curDateTime.month, curDateTime.day,
+                                                                    curDateTime.hour, curDateTime.minute), ChConfig.TYPE_Time_Format)
+    
+    randMailKey = ""
+    sysAuctionItemListDict = IpyGameDataPY.GetConfigEx("SysWaitAuctionItemListDict") # 系统等待上架的拍品列表字典 {cfgID:[待上架拍品列表], ...}
+    if not sysAuctionItemListDict:
+        sysAuctionItemListDict = {}
+        
+    curTime = int(time.time())
+    addItemTime = curTime
+    openJobList = IpyGameDataPY.GetFuncEvalCfg("OpenJob", 1)
+    auctionItemMgr = PyDataManager.GetAuctionItemManager()
+    curWorldLV = PlayerDBGSEvent.GetDBGSTrig_ByKey(ShareDefine.Def_Notify_WorldKey_WorldAverageLv)
+    addSystemAuctionItemInfo = __GetAuctionSystemItemInfo()
+    for cfgID, ipyData, startDatetime, endDatetime, addAuctionItemDatetimeList in addSystemAuctionItemInfo:
+        #cfgID = ipyData.GetCfgID()
+        if cfgID in sysAuctionItemListDict:
+            #GameWorld.DebugLog("    队列中还有未处理的拍品时不处理,防止重复添加!cfgID=%s" % (cfgID))
+            continue
+        worldLVRange = ipyData.GetWorldLVRange()
+        if worldLVRange and len(worldLVRange) == 2:
+            worldLVMin, worldLVMax = worldLVRange
+            if curWorldLV < worldLVMin or curWorldLV > worldLVMax:
+                #GameWorld.DebugLog("    不满足当前世界等级范围条件,不处理该系统上架拍品计划!cfgID=%s,curWorldLV=%s" % (cfgID, curWorldLV))
+                continue
+            
+        if curDateTime < startDatetime or curDateTime > endDatetime:
+            #GameWorld.DebugLog("    不在规定的时间内,不处理该系统上架拍品计划!cfgID=%s" % (cfgID))
+            continue
+        
+        randSecondRange = ipyData.GetAddRandSecondRange()
+        if len(randSecondRange) != 2:
+            #GameWorld.DebugLog("    随机上架秒数格式错误,不处理该系统上架拍品计划!cfgID=%s" % (cfgID))
+            continue
+        
+        addItemInfoList = []
+        replenishAuctionCount = ipyData.GetReplenishAuctionCount()
+        # 动态模式
+        if replenishAuctionCount:
+            replenishCDSeconds = ipyData.GetReplenishCDMinutes() * 60
+            lastReplenishTime = auctionItemMgr.worldAuctionReplenishTimeDict.get(cfgID, 0)
+            if curTime - lastReplenishTime < replenishCDSeconds:
+                continue
+            auctionItemMgr.worldAuctionReplenishTimeDict[cfgID] = curTime
+            
+            replenishItemID = ipyData.GetReplenishItemID()
+            if replenishItemID:
+                curItemIDCount = auctionItemMgr.worldAuctionItemCountDict.get(replenishItemID, 0)
+                if curItemIDCount >= replenishAuctionCount:
+                    continue
+                addItemIDCount = replenishAuctionCount - curItemIDCount
+                GameWorld.DebugLog("    动态补充拍品队列: cfgID=%s,replenishItemID=%s,addItemIDCount=%s" % (cfgID, replenishItemID, addItemIDCount))
+                for _ in xrange(addItemIDCount):
+                    addItemInfoList.append(replenishItemID)
+            else:
+                replenishEquipPlaces = ipyData.GetReplenishEquipPlaces()
+                rpClassLV, rpColor, rpIsSuit = ipyData.GetReplenishEquipInfo()
+                rpStar = 0
+                for job in openJobList:
+                    jobEquipKey = (job, rpClassLV, rpColor, rpIsSuit)
+                    curJobEquipCount = auctionItemMgr.worldAuctionJobEquipCountDict.get(jobEquipKey, 0)
+                    if curJobEquipCount >= replenishAuctionCount:
+                        continue
+                    addEquipCount = replenishAuctionCount - curJobEquipCount
+                    GameWorld.DebugLog("    动态补充拍品队列: cfgID=%s,addEquipCount=%s,job=%s" % (cfgID, addEquipCount, job))
+                    for _ in xrange(addEquipCount):
+                        addItemInfoList.append([rpClassLV, rpColor, replenishEquipPlaces, rpIsSuit, rpStar, [job]])
+                        
+            random.shuffle(addItemInfoList) # 动态模式待添加拍品打乱下顺序,防止批量添加同一职业物品
+            
+        # 指定模式
+        elif curDateTime in addAuctionItemDatetimeList:
+            
+            addCountWeightList = ipyData.GetItemCountWeightList()
+            auctionItemWeightList = ipyData.GetAuctionItemWeightList()
+            
+            addCount = GameWorld.GetResultByWeightList(addCountWeightList)
+            GameWorld.DebugLog("    指定补充拍品队列: cfgID=%s,addCount=%s" % (cfgID, addCount))
+            for _ in xrange(addCount):
+                itemInfo = GameWorld.GetResultByWeightList(auctionItemWeightList)
+                if itemInfo != None:
+                    addItemInfoList.append(itemInfo)
+                    
+            randMailKeyList = ipyData.GetRandMailKeyList()
+            if randMailKeyList:
+                randMailKey = random.choice(randMailKeyList)
+            
+        sysWaitAuctionItemList = []
+        for itemInfo in addItemInfoList:
+            randSeconds = random.randint(randSecondRange[0], randSecondRange[1])
+            addItemTime = addItemTime + randSeconds
+            sysWaitAuctionItemList.append([addItemTime, itemInfo])
+        sysAuctionItemListDict[cfgID] = sysWaitAuctionItemList
+        
+    IpyGameDataPY.SetConfigEx("SysWaitAuctionItemListDict", sysAuctionItemListDict)
+    #GameWorld.DebugLog("等待系统上架的拍品列表: %s" % sysAuctionItemListDict)
+    
+    # 随机邮件通知 
+    if randMailKey:
+        playerIDList = []
+        addItemList = []
+        playerManager = GameWorld.GetPlayerManager()
+        for i in xrange(playerManager.GetActivePlayerCount()):
+            player = playerManager.GetActivePlayerAt(i)
+            if player == None:
+                continue
+            if PlayerControl.GetIsTJG(player):
+                continue
+            playerIDList.append(player.GetPlayerID())
+        PlayerCompensation.SendMailByKey(randMailKey, playerIDList, addItemList)
+               
+    return
+
+def __DoSysWaitAddAuctionItem(tick):
+    SysWaitAuctionItemListDict = IpyGameDataPY.GetConfigEx("SysWaitAuctionItemListDict") # 系统等待上架的拍品列表
+    if not SysWaitAuctionItemListDict:
+        return
+    
+    curTime = int(time.time())
+    for cfgID, sysAuctionItemList in SysWaitAuctionItemListDict.items():
+        doCount = len(sysAuctionItemList)
+        while doCount > 0 and sysAuctionItemList:
+            doCount -= 1
+            addItemTime, itemInfo = sysAuctionItemList[0]
+            if curTime < addItemTime:
+                #GameWorld.DebugLog("未到系统等待上架的拍品时间,不处理! curTime=%s,sysAuctionItemList=%s" % (curTime, sysAuctionItemList))
+                break
+            sysAuctionItemList.pop(0)
+            GameWorld.DebugLog("系统等待上架的拍品时间已到,可上架! curTime=%s >= addItemTime=%s,itemInfo=%s,sysAuctionItemList=%s" % (curTime, addItemTime, itemInfo, sysAuctionItemList))
+            DoAddSystemAuctionItem([itemInfo])
+            
+        if not sysAuctionItemList:
+            SysWaitAuctionItemListDict.pop(cfgID)
+            
+    return
+
+def OnAuctionItemTimeProcess(curTime, tick):
     ## 拍卖行拍品定时处理,每秒触发一次
+    
+    __DoSysBuyoutItemByTime(curTime)
+    
+    __DoSysWaitAddAuctionItem(tick)
+    
     allAuctionItemByEndTimeList = PyDataManager.GetAuctionItemManager().allAuctionItemByEndTimeList
     if not allAuctionItemByEndTimeList:
         return
     
+    index = 0
     endItemList = [] # 结束竞价的拍品列表
     moveToWorldItemList = [] # 转移到全服拍卖的仙盟拍品列表
     doCount = len(allAuctionItemByEndTimeList)
     while doCount > 0 and allAuctionItemByEndTimeList:
         doCount -= 1
-        auctionItem = allAuctionItemByEndTimeList[0]
-        if curTime < auctionItem.EndTime:
+        auctionItem = allAuctionItemByEndTimeList[index]
+        if curTime <= auctionItem.EndTime:
             break
-        allAuctionItemByEndTimeList.pop(0)
+        if auctionItem.BiddingQueryTick and tick - auctionItem.BiddingQueryTick < BiddingQueryLockTick:
+            index += 1
+            continue
+        allAuctionItemByEndTimeList.pop(index)
         
         # 没有人竞价的仙盟拍品
         if not auctionItem.BidderPrice and auctionItem.FamilyID and auctionItem.AuctionType == AuctionType_Family:
@@ -329,6 +711,89 @@
     __EndAuctionItem(endItemList, "ByTime")
     __MoveFamilyAuctionItemToWorld(moveToWorldItemList)
     return
+
+def __DoSysBuyoutItemByTime(curTime):
+    ## 系统一口价拍品
+    
+    sysBuyoutItemByTimeList = PyDataManager.GetAuctionItemManager().sysBuyoutItemByTimeList
+    if not sysBuyoutItemByTimeList:
+        return
+    
+    sysAuctionItemList = []
+    color = 4 # 固定橙色
+    isSuit = 0 # 固定非套装 
+    star = 0 # 固定0星
+    
+    endItemList = [] # 结束竞价的拍品列表
+    doCount = len(sysBuyoutItemByTimeList)
+    while doCount > 0 and sysBuyoutItemByTimeList:
+        doCount -= 1
+        auctionItem = sysBuyoutItemByTimeList[0]
+        if curTime <= auctionItem.SysBuyTime:
+            break
+        sysBuyoutItemByTimeList.pop(0)
+        
+        if auctionItem.BidderPrice or auctionItem.BidderID:
+            continue
+        
+        classLV = auctionItem.ItemClassLV
+        if not classLV:
+            continue        
+        
+        itemID = auctionItem.ItemID
+        ipyData = IpyGameDataPY.GetIpyGameData("AuctionItem", itemID)
+        if not ipyData:
+            continue
+        buyoutPrice = ipyData.GetBuyoutPrice()
+        if not buyoutPrice:
+            continue
+        
+        buyoutPrice *= auctionItem.Count
+        auctionItem.BidderPrice = buyoutPrice # 没人竞价的系统直接一口价回收
+        endItemList.append(auctionItem)
+        
+        # 生成系统补上架的装备信息
+        randPlaceList = IpyGameDataPY.GetFuncEvalCfg("AuctionItemSystem", 4)
+        if not randPlaceList:
+            curPlace = itemID % 1000 / 10 # 倒数2、3位代表部位
+            placeList = [curPlace]
+        else:
+            placeList = randPlaceList
+            
+        totalPlayerCount = 0
+        jobWeightList = []
+        openJobList = IpyGameDataPY.GetFuncEvalCfg("OpenJob", 1)
+        for job in openJobList:
+            jobPlayerCount = len(PyGameData.g_onedayJobPlayerLoginoffTimeDict.get(job, {}))
+            jobPlayerCount = max(1, jobPlayerCount) # 人数默认至少1个
+            totalPlayerCount += jobPlayerCount
+            jobWeightList.append([jobPlayerCount, job])
+            GameWorld.DebugLog("职业人数: job=%s,count=%s" % (job, jobPlayerCount))
+            
+        maxJobPer = IpyGameDataPY.GetFuncCfg("AuctionItemSystem", 3) # 单职业最大百分比
+        if maxJobPer and maxJobPer < 100 and totalPlayerCount:
+            minJobPer = 100 - maxJobPer # 单职业至少百分比
+            for jobWeightInfo in jobWeightList:
+                jobPlayerCount = jobWeightInfo[0]
+                jobPer = int(jobPlayerCount * 100.0 / totalPlayerCount)
+                jobPer = min(max(minJobPer, jobPer), maxJobPer)
+                jobWeightInfo[0] = jobPer
+        GameWorld.DebugLog("随机上架职业装备比重: jobWeightList=%s" % jobWeightList)
+        job = GameWorld.GetResultByWeightList(jobWeightList)
+        itemJobList = [job] if job != None else openJobList
+        sysAuctionItemList.append([classLV, color, placeList, isSuit, star, itemJobList])
+        
+    if not endItemList:
+        return
+    
+    __EndAuctionItem(endItemList, "SysBuyout")
+    
+    # 系统回收拍品后立即按规则补上架拍品
+    if sysAuctionItemList:
+        DoAddSystemAuctionItem(sysAuctionItemList)
+        
+    return
+
 
 def __MoveFamilyAuctionItemToWorld(auctionItemList):
     ## 仙盟拍品转移到全服
@@ -354,7 +819,10 @@
         
         auctionItemMgr.allAuctionItemByEndTimeList.append(auctionItem)
         auctionItemMgr.worldAuctionItemList.append(auctionItem)
+        __OnCalcWorldAuctionItemCount(auctionItem, 1)
         notifyWorldAddItemList.append([itemGUID, itemID, playerID])
+        
+        AddAuctionRecord(auctionItem, AuctionRecordResult_MoveToWorld)
         
         # 添加进我的关注
         for attentionPlayerID, attentionList in auctionItemMgr.myAttentionItemDict.items():
@@ -399,9 +867,10 @@
     if isSortWorldItem:
         auctionItemMgr.worldAuctionItemList.sort(key=operator.attrgetter("Sortpriority", "AddTime"))
         auctionItemMgr.worldAuctionItemQueryDict = {} # 重置全服拍品条件查询,下次有玩家查询时再重新刷新
+        auctionItemMgr.sysBuyoutItemByTimeList.sort(key=operator.attrgetter("SysBuyTime"))
     return
 
-def __EndAuctionItem(endItemList, endEvent):
+def __EndAuctionItem(endItemList, endEvent, funcAutoBuyout=False, buyPlayer=None):
     ''' 结束拍品竞拍
     @param delItemStateDict: 删除的拍品竞拍状态
     '''
@@ -423,59 +892,90 @@
         bidderPrice = auctionItem.BidderPrice # 当前竞价,有人竞价的话代表竞拍成功
         endType = ""
         # 有人竞价,成交
-        if bidderID and bidderPrice:
+        if bidderPrice:
             endType = "OK"
             
             # 竞拍成功邮件,发放物品
-            paramList = [bidderPrice]
-            detail = {"ItemGUID":itemGUID}
-            addItemList = [{"ItemID":itemID, "Count":itemCount, "IsBind":True, "UserData":auctionItem.UserData}]
-            PlayerCompensation.SendMailByKey("PaimaiMail3", [bidderID], addItemList, paramList, detail=detail)
-            AddAuctionRecord(auctionItem, AuctionRecordResult_BidOK)
+            if bidderID:
+                mailTypeKey = "PaimaiMail3"
+                paramList = [bidderPrice]
+                detail = {"ItemGUID":itemGUID}
+                addItemList = [{"ItemID":itemID, "Count":itemCount, "IsAuctionItem":False, "UserData":auctionItem.UserData}]
+                if funcAutoBuyout:
+                    # 功能自动购买的不给物品,由功能根据功能需求处理
+                    pass
+                ## 如果有玩家的,直接给到背包
+                elif buyPlayer and buyPlayer.GetPlayerID() == bidderID:
+                    mailInfo = [mailTypeKey, addItemList, paramList, detail]
+                    resultMsg = str([itemGUID, itemID, itemCount, auctionItem.UserData, mailInfo])
+                    buyPlayer.MapServer_QueryPlayerResult(0, 0, "AuctionHouseGiveItem", resultMsg, len(resultMsg))
+                else:
+                    PlayerCompensation.SendMailByKey(mailTypeKey, [bidderID], addItemList, paramList, detail=detail)
+                AddAuctionRecord(auctionItem, AuctionRecordResult_BidOK)
             
             # 拍卖成功收益,都以玩家收益向上取整
             if familyID and auctionItem.FamilyPlayerIDInfo:
                 familyPlayerIDList = json.loads(auctionItem.FamilyPlayerIDInfo)
-                taxRate = IpyGameDataPY.GetFuncCfg("AuctionTaxrate", 2) # 仙盟拍品税率百分比
-                personMaxRate = IpyGameDataPY.GetFuncCfg("AuctionTaxrate", 3) # 仙盟拍品个人最大收益百分比
-                giveTotalGold = int(math.ceil(bidderPrice * (100 - taxRate) / 100.0))
-                giveMaxGold = int(math.ceil(giveTotalGold * personMaxRate / 100.0))
-                memCount = len(familyPlayerIDList)
-                giveGoldAverage = min(giveMaxGold, int(math.ceil(giveTotalGold * 1.0 / memCount))) # 有收益的人平分
-                
-                # 仙盟拍品收益邮件
-                detail = {"ItemGUID":itemGUID, "ItemID":itemID, "Count":itemCount, "BidderPrice":bidderPrice, "FamilyPlayerIDList":familyPlayerIDList,
-                          ChConfig.Def_MailMoneySource:ChConfig.Def_GiveMoney_AuctionGain}
-                paramList = [itemID, auctionItem.BidderName, bidderPrice, taxRate, giveGoldAverage, personMaxRate]
-                PlayerCompensation.SendMailByKey("PaimaiMail6", familyPlayerIDList, [], paramList, gold=giveGoldAverage, detail=detail)
-                
+                if familyPlayerIDList:
+                    taxRate = IpyGameDataPY.GetFuncCfg("AuctionTaxrate", 2) # 仙盟拍品税率百分比
+                    personMaxRate = IpyGameDataPY.GetFuncCfg("AuctionTaxrate", 3) # 仙盟拍品个人最大收益百分比
+                    taxGold = max(1, int(bidderPrice * taxRate / 100.0)) # 最少收税1
+                    giveTotalGold = max(0, bidderPrice - taxGold)
+                    giveMaxGold = int(math.ceil(giveTotalGold * personMaxRate / 100.0))
+                    memCount = len(familyPlayerIDList)
+                    giveGoldAverage = min(giveMaxGold, int(math.ceil(giveTotalGold * 1.0 / memCount))) # 有收益的人平分
+                    
+                    # 仙盟拍品收益邮件
+                    detail = {"ItemGUID":itemGUID, "ItemID":itemID, "Count":itemCount, "BidderPrice":bidderPrice, "FamilyPlayerIDList":familyPlayerIDList}
+                    paramList = [itemID, itemID, bidderPrice, taxRate, giveGoldAverage, personMaxRate]
+                    PlayerCompensation.SendMailByKey("PaimaiMail8", familyPlayerIDList, [], paramList, goldPaper=giveGoldAverage, 
+                                                     detail=detail, moneySource=ChConfig.Def_GiveMoney_AuctionGain)
+                else:
+                    GameWorld.ErrLog("仙盟拍品没有人获得收益!familyID=%s,itemID=%s,itemGUID=%s" % (familyID, itemID, itemGUID))
+                    
             elif playerID:
                 taxRate = IpyGameDataPY.GetFuncCfg("AuctionTaxrate", 1) # 全服拍品税率百分比
-                givePlayerGold = int(math.ceil(bidderPrice * (100 - taxRate) / 100.0))
+                taxGold = max(1, int(bidderPrice * taxRate / 100.0)) # 最少收税1
+                givePlayerGold = max(0, bidderPrice - taxGold)
                 
                 # 个人拍卖收益邮件
-                detail = {"ItemGUID":itemGUID, "ItemID":itemID, "Count":itemCount, "BidderPrice":bidderPrice, 
-                          ChConfig.Def_MailMoneySource:ChConfig.Def_GiveMoney_AuctionGain}
-                paramList = [itemID, auctionItem.BidderName, bidderPrice, taxRate, givePlayerGold]
-                PlayerCompensation.SendMailByKey("PaimaiMail5", [playerID], [], paramList, gold=givePlayerGold, detail=detail)
+                detail = {"ItemGUID":itemGUID, "ItemID":itemID, "Count":itemCount, "BidderPrice":bidderPrice}
+                paramList = [itemID, itemID, bidderPrice, taxRate, givePlayerGold]
+                PlayerCompensation.SendMailByKey("PaimaiMail7", [playerID], [], paramList, goldPaper=givePlayerGold, 
+                                                 detail=detail, moneySource=ChConfig.Def_GiveMoney_AuctionGain)
+                
+            else:
+                GameWorld.Log("系统拍品成交: itemGUID=%s,itemID=%s" % (itemGUID, itemID))
                 
             AddAuctionRecord(auctionItem, AuctionRecordResult_SellOK)
+            
+            #策划需求屏蔽掉成交广播
+            #ipyData = IpyGameDataPY.GetIpyGameData("AuctionItem", itemID)
+            #if ipyData and ipyData.GetNeedWorldNotify():
+            #    PlayerControl.WorldNotify(0, "Paimai6", [auctionItem.BidderName, bidderID, auctionItem.AuctionType, bidderPrice, itemID])
         else:
             # 仙盟拍品回收
             if familyID:
                 endType = "Recycle"
                 AddAuctionRecord(auctionItem, AuctionRecordResult_Recycle)
             # 个人拍品流拍,物品返还
-            else:
+            elif playerID:
                 endType = "Return"
                 
-                # 流拍返还物品邮件
+                # 返还物品邮件
                 paramList = []
                 detail = {"ItemGUID":itemGUID}
-                addItemList = [{"ItemID":itemID, "Count":itemCount, "IsBind":True, "UserData":auctionItem.UserData}]
-                PlayerCompensation.SendMailByKey("PaimaiMail4", [playerID], addItemList, paramList, detail=detail)
-                
-                AddAuctionRecord(auctionItem, AuctionRecordResult_SellFail)
+                addItemList = [{"ItemID":itemID, "Count":itemCount, "IsAuctionItem":True, "UserData":auctionItem.UserData}]
+                # 下架
+                if endEvent == "Unsell":
+                    PlayerCompensation.SendMailByKey("PaimaiMail9", [playerID], addItemList, paramList, detail=detail)
+                    AddAuctionRecord(auctionItem, AuctionRecordResult_Unsell)
+                else:
+                    PlayerCompensation.SendMailByKey("PaimaiMail4", [playerID], addItemList, paramList, detail=detail)
+                    AddAuctionRecord(auctionItem, AuctionRecordResult_SellFail)
+            else:
+                endType = "SystemDelete"
+                GameWorld.Log("系统拍品流拍: itemGUID=%s,itemID=%s" % (itemGUID, itemID))
                 
         drDict = {"AuctionItemInfo":__GetAuctionItemDRDict(auctionItem), "EndType":endType, "EndEvent":endEvent}
         DR_AuctionHouse(None, "EndAuctionItem", drDict)
@@ -490,6 +990,7 @@
             
         if auctionItem in auctionItemMgr.worldAuctionItemList:
             auctionItemMgr.worldAuctionItemList.remove(auctionItem)
+            __OnCalcWorldAuctionItemCount(auctionItem, -1)
             
         for queryItemList in auctionItemMgr.worldAuctionItemQueryDict.values():
             if auctionItem in queryItemList:
@@ -500,6 +1001,9 @@
             if auctionItem in familyItemList:
                 familyItemList.remove(auctionItem)
                 
+        if auctionItem in auctionItemMgr.sysBuyoutItemByTimeList:
+            auctionItemMgr.sysBuyoutItemByTimeList.remove(auctionItem)
+            
         for nowBiddingItemList in auctionItemMgr.nowBiddingAuctionItemDict.values():
             if auctionItem in nowBiddingItemList:
                 nowBiddingItemList.remove(auctionItem)
@@ -588,6 +1092,19 @@
             
         return
     
+    # 下架拍品
+    elif queryType == "UnsellAuctionItem":
+        itemGUID = queryData[0]
+        __DoUnsellAuctionItem(curPlayer, itemGUID)
+        return
+        
+    # 升星自动购买
+    elif queryType == "EquipStarAutoBuy":
+        buyResult = __DoEquipStarAutoBuyEquip(curPlayer, queryData,  tick)
+        if buyResult == None:
+            return
+        result = buyResult
+        
     elif queryType == "ClearAuctionItem":
         __DoGMClearAuctionItem(curPlayer)
         return
@@ -619,35 +1136,37 @@
     @param curPlayer: 可能为None
     '''
     
-    notifyWorldAddItemList = [] # 新增全服拍品通知 [[itemGUID, itemID, playerID], ...]
+    isSortWorldItem = False
+    notifyAddItemList = [] # 新增拍品通知 [[itemGUID, itemID, playerID], ...]
     notifyFamilyAddItemDict = {} # 新增仙盟拍品通知 {familyID:[auctionItem, ...], ...}
     for playerID, familyID, familyPlayerIDList, itemData in addAuctionItemList:
-        if not playerID and not familyID:
-            continue
+        #系统拍品玩家ID、仙盟ID都为0,可上架,这里不再限制
+        #if not playerID and not familyID:
+        #    continue
         
         auctionItem = __DoAddAuctionItem(curPlayer, playerID, familyID, familyPlayerIDList, itemData)
         if not auctionItem:
             continue
         
+        itemGUID = auctionItem.ItemGUID
+        itemID = auctionItem.ItemID
+        notifyAddItemList.append([itemGUID, itemID, playerID])
         if familyID:
             familyAddItemList = notifyFamilyAddItemDict.get(familyID, [])
             familyAddItemList.append(auctionItem)
             notifyFamilyAddItemDict[familyID] = familyAddItemList
         else:
-            itemGUID = auctionItem.ItemGUID
-            itemID = auctionItem.ItemID
-            notifyWorldAddItemList.append([itemGUID, itemID, playerID])
+            isSortWorldItem = True
             
-    if notifyFamilyAddItemDict or notifyWorldAddItemList:
-        isSortWorldItem = notifyWorldAddItemList != []
+    if notifyAddItemList:
         __SortAuctionitem(isSortWorldItem=isSortWorldItem)
         
     # 通知新增仙盟拍品
     for familyID, familyAddItemList in notifyFamilyAddItemDict.items():
         Sync_FamilyAuctionItemInfo(None, familyID, familyAddItemList)
         
-    # 通知全服拍品关注玩家
-    __NotifyAuctionPlayerAddItem(notifyWorldAddItemList)
+    # 通知拍品关注玩家
+    __NotifyAuctionPlayerAddItem(notifyAddItemList)
     return
 
 def __DoAddAuctionItem(curPlayer, playerID, familyID, familyPlayerIDList, itemData):
@@ -672,7 +1191,9 @@
     auctionItem.UserDataLen = len(auctionItem.UserData)
     auctionItem.FamilyPlayerIDInfo = str(familyPlayerIDList)
     auctionItem.FamilyPlayerIDLen = len(auctionItem.FamilyPlayerIDInfo)
-            
+    auctionItem.AuctionType = AuctionType_Family if familyID else AuctionType_World
+    GameWorld.Log("上架拍品: playerID=%s,familyID=%s,itemID=%s,auctionType=%s" % (playerID, familyID, itemID, auctionItem.AuctionType))
+    
     if not __InitAuctionItemAttrEx(auctionItem):
         return
     
@@ -681,13 +1202,12 @@
     auctionItemMgr.allAuctionItemByEndTimeList.append(auctionItem)
     
     if familyID:
-        auctionItem.AuctionType = AuctionType_Family
         familyItemList = auctionItemMgr.familyAuctionItemDict.get(familyID, [])
         familyItemList.append(auctionItem)
         auctionItemMgr.familyAuctionItemDict[familyID] = familyItemList
     else:
-        auctionItem.AuctionType = AuctionType_World
         auctionItemMgr.worldAuctionItemList.append(auctionItem)
+        __OnCalcWorldAuctionItemCount(auctionItem, 1)
         
         # 添加进我的拍卖
         if playerID:
@@ -714,9 +1234,9 @@
     GameWorld.DebugLog("更新拍品数: %s" % len(auctionItemMgr.allAuctionItemDict))
     return auctionItem
 
-def __NotifyAuctionPlayerAddItem(notifyWorldAddItemList):
+def __NotifyAuctionPlayerAddItem(notifyAddItemList):
     ## 通知关注物品的玩家新上架物品了
-    if not notifyWorldAddItemList:
+    if not notifyAddItemList:
         return
     attentionMgr = PyDataManager.GetAuctionAttentionManager()
     playerManager = GameWorld.GetPlayerManager()
@@ -730,8 +1250,8 @@
         if not playerAttentionIDList:
             continue
         infoPack = None
-        for itemGUID, itemID, playerID in notifyWorldAddItemList:
-            if playerID == player.GetPlayerID():
+        for itemGUID, itemID, playerID in notifyAddItemList:
+            if playerID and playerID == player.GetPlayerID():
                 # 自己上架的物品不通知
                 continue
             if itemID not in playerAttentionIDList:
@@ -748,12 +1268,38 @@
             NetPackCommon.SendFakePack(player, infoPack)
     return
 
-def __DoPlayerBidAuctionItem(curPlayer, itemGUID, biddingPrice, tick, isOnlyCheck):
+def __DoUnsellAuctionItem(curPlayer, itemGUID):
+    ## 下架拍品
+    auctionItem = GetAuctionItem(itemGUID)
+    if not auctionItem:
+        # 拍品不存在
+        PlayerControl.NotifyCode(curPlayer, "Paimai3")
+        return
+    playerID = curPlayer.GetPlayerID()
+    itemID = auctionItem.ItemID
+    if auctionItem.FamilyID:
+        GameWorld.ErrLog("仙盟拍品无法下架!itemGUID=%s,itemID=%s,itemFamilyID=%s" 
+                         % (itemGUID, itemID, auctionItem.FamilyID), playerID)
+        return
+    if auctionItem.PlayerID != playerID:
+        GameWorld.ErrLog("不是玩家自己的拍品无法下架!itemGUID=%s,itemID=%s,itemPlayerID=%s" 
+                         % (itemGUID, itemID, auctionItem.PlayerID), playerID)
+        return
+    if auctionItem.BidderPrice:
+        # 竞价中的拍品不能下架
+        PlayerControl.NotifyCode(curPlayer, "Paimai9")
+        return
+    
+    __EndAuctionItem([auctionItem], "Unsell")
+    return
+
+def __DoPlayerBidAuctionItem(curPlayer, itemGUID, biddingPrice, tick, isOnlyCheck, funcAutoBuyout=False):
     ''' 玩家竞价物品
     @param curPlayer: 竞价的玩家
     @param itemGUID: 拍品GUID
     @param biddingPrice: 竞价
     @param isOnlyCheck: 是否仅检查可否竞价
+    @param funcAutoBuyout: 是否功能自动购买
     '''
     
     errInfo = ""
@@ -789,13 +1335,13 @@
                              % (itemGUID, itemID, itemFamilyID, playerFamilyID), playerID)
             errInfo = "is family auction item"
             return itemID, errInfo
-    if curTime >= auctionItem.EndTime:
+    if curTime > auctionItem.EndTime:
         GameWorld.ErrLog("拍品已结束竞价! itemGUID=%s,itemID=%s,addTimeStr=%s" % (itemGUID, itemID, addTimeStr), playerID)
         errInfo = "end bid"
         return itemID, errInfo
     
-    nextPrice = ipyData.GetBasePrice() if not auctionItem.BidderPrice else (auctionItem.BidderPrice + ipyData.GetBiddingAdd())
-    buyoutPrice = ipyData.GetBuyoutPrice() # 允许没有一口价的,代表可以一直竞价
+    nextPrice = ipyData.GetBasePrice() * auctionItem.Count if not auctionItem.BidderPrice else (auctionItem.BidderPrice + ipyData.GetBiddingAdd() * auctionItem.Count)
+    buyoutPrice = ipyData.GetBuyoutPrice() * auctionItem.Count # 允许没有一口价的,代表可以一直竞价
     nextPrice = nextPrice if not buyoutPrice else min(nextPrice, buyoutPrice) # 不超过一口价
     if not (biddingPrice == nextPrice or (buyoutPrice and biddingPrice == buyoutPrice)):
         # 竞价价格错误
@@ -805,7 +1351,7 @@
     
     if isOnlyCheck:
         queryTick = auctionItem.BiddingQueryTick
-        if queryTick and tick - queryTick < 10000:
+        if queryTick and tick - queryTick < BiddingQueryLockTick:
             # 有玩家正在竞价,请稍等
             PlayerControl.NotifyCode(curPlayer, "Paimai1")
             errInfo = "other player bidding"
@@ -814,7 +1360,7 @@
         auctionItem.BiddingQueryTick = tick
         return itemID, errInfo
     
-    if auctionItem.BiddingQueryID != playerID:
+    if not funcAutoBuyout and auctionItem.BiddingQueryID != playerID:
         PlayerControl.NotifyCode(curPlayer, "Paimai2")
         errInfo = "bidding player error"
         return itemID, errInfo
@@ -831,15 +1377,17 @@
     
     # 邮件返还上个竞价者
     if lastBidderID and lastBidderPrice:
-        detail = {"ItemID":itemID, "ItemGUID":itemGUID, "Count":itemCount, ChConfig.Def_MailMoneySource:ChConfig.Def_GiveMoney_AuctionBidReturn}
+        detail = {"ItemID":itemID, "ItemGUID":itemGUID, "Count":itemCount}
         if isBuyout:
             # 竞拍失败,仅通知
-            paramList = [itemID, lastBidderPrice]
-            PlayerCompensation.SendMailByKey("PaimaiMail2", [lastBidderID], [], paramList, gold=lastBidderPrice, detail=detail)
+            paramList = [itemID, itemID, lastBidderPrice]
+            PlayerCompensation.SendMailByKey("PaimaiMail2", [lastBidderID], [], paramList, goldPaper=lastBidderPrice, 
+                                             detail=detail, moneySource=ChConfig.Def_GiveMoney_AuctionBidReturn)
         else:
             # 竞拍失败,可继续竞价邮件
-            paramList = [itemID, lastBidderPrice, itemGUID]
-            PlayerCompensation.SendMailByKey("PaimaiMail1", [lastBidderID], [], paramList, gold=lastBidderPrice, detail=detail)
+            paramList = [itemID, itemID, lastBidderPrice, itemGUID]
+            PlayerCompensation.SendMailByKey("PaimaiMail1", [lastBidderID], [], paramList, goldPaper=lastBidderPrice, 
+                                             detail=detail, moneySource=ChConfig.Def_GiveMoney_AuctionBidReturn)
         AddAuctionRecord(auctionItem, AuctionRecordResult_BidFail)
         
         isSyncBiddingItem = False
@@ -860,7 +1408,7 @@
                 
     # 更新竞价信息
     auctionItem.BiddingQueryID = 0
-    auctionItem.BiddingTick = 0
+    auctionItem.BiddingQueryTick = 0
     auctionItem.BiddingTime = GameWorld.GetCurrentDataTimeStr()
     auctionItem.BidderID = playerID
     auctionItem.BidderName = curPlayer.GetName()
@@ -887,8 +1435,12 @@
     GameWorld.DebugLog("玩家竞价拍品: itemGUID=%s,itemID=%s,isBuyout=%s,lastBidderID=%s,lastBidderPrice=%s,bidderIDInfo=%s" 
                        % (itemGUID, itemID, isBuyout, lastBidderID, lastBidderPrice, auctionItem.BidderIDInfo), playerID)
     
-    if isBuyout:        
-        __EndAuctionItem([auctionItem], "Buyout")
+    if auctionItem in auctionItemMgr.sysBuyoutItemByTimeList:
+        auctionItemMgr.sysBuyoutItemByTimeList.remove(auctionItem)
+        #GameWorld.DebugLog("拍品有人竞价了,移除系统一口价拍品列表!")
+        
+    if isBuyout:
+        __EndAuctionItem([auctionItem], "Buyout", funcAutoBuyout, buyPlayer=curPlayer)
     else:
         if __AddAuctionItemEndTimeByBid(auctionItem):
             __SortAuctionitem(isSortWorldItem=False)
@@ -899,6 +1451,183 @@
         __SyncRefreshAuctionItem([auctionItem])
         
     return itemID, errInfo
+
+def __DoEquipStarAutoBuyEquip(curPlayer, queryData, tick):
+    ## 升星自动购买
+    classLV, equipPlace, curPartStar, equipPackIndex, isAutoBuyPreview, curRate, delEquipGUIDDict, delItemInfoDict, lackItemCostMoney, playerGoldPaper = queryData
+    GameWorld.DebugLog("升星自动购买装备: classLV=%s, equipPlace=%s, curPartStar=%s, equipPackIndex=%s" % (classLV, equipPlace, curPartStar, equipPackIndex))
+    GameWorld.DebugLog("    是否预览 %s, curRate=%s,lackItemCostMoney=%s, playerGoldPaper=%s" % (isAutoBuyPreview, curRate, lackItemCostMoney, playerGoldPaper))
+    nextStar = curPartStar + 1
+    ipyData = IpyGameDataPY.GetIpyGameData("EquipStarUp", classLV, equipPlace, nextStar)
+    if not ipyData:
+        return
+    costEquipPlaceList = ipyData.GetCostEquipPlace()
+    costEquipColorList = ipyData.GetCostEquipColor()
+    isJobLimit = ipyData.GetIsJobLimit()
+    unSuitRate = ipyData.GetUnSuitRate()
+    
+    curTime = int(time.time())
+    fullRate = IpyGameDataPY.GetFuncCfg("EquipStarRate", 4)
+    autoBuyOtherClassItemDict = {}
+    buyEquipCostMoney = 0
+    autoBuyItemList = []
+    auctionItemMgr = PyDataManager.GetAuctionItemManager()
+    #GameWorld.DebugLog("世界拍品个数: %s" % len(auctionItemMgr.worldAuctionItemList))
+    for i, worldAuctionItem in enumerate(auctionItemMgr.worldAuctionItemList):
+                
+        itemID = worldAuctionItem.ItemID
+        aucItemJob = worldAuctionItem.ItemJobLimit
+        if not aucItemJob:
+            #GameWorld.DebugLog("    %s 职业通用的, 不购买!itemID=%s" % (i, itemID))
+            continue
+        if isJobLimit and aucItemJob != curPlayer.GetJob():
+            #GameWorld.DebugLog("    %s 职业不可用, 不购买!itemID=%s,aucItemJob=%s != %s" % (i, itemID, aucItemJob, curPlayer.GetJob()))
+            continue
+        
+        itemIDStr = str(itemID)
+        aucItemColor = int(itemIDStr[3:4])
+        aucItemPlace = int(itemIDStr[4:6])
+        aucItemIsSuit = int(itemIDStr[-1])
+        
+        if aucItemColor not in costEquipColorList:
+            #GameWorld.DebugLog("    %s 颜色限制, 不购买!itemID=%s,aucItemColor=%s not in %s" % (i, itemID, aucItemColor, costEquipColorList))
+            continue
+        if aucItemPlace not in costEquipPlaceList:
+            #GameWorld.DebugLog("    %s 装备位限制, 不购买!itemID=%s,aucItemPlace=%s not in %s" % (i, itemID, aucItemPlace, costEquipPlaceList))
+            continue
+        if aucItemIsSuit:
+            #套装不允许自动购买
+            #GameWorld.DebugLog("    %s 套装, 不购买!itemID=%s" % (i, itemID))
+            continue
+        
+        aucIpyData = IpyGameDataPY.GetIpyGameData("AuctionItem", itemID)
+        if not aucIpyData:
+            continue
+        buyoutPrice = aucIpyData.GetBuyoutPrice()
+        if not buyoutPrice:
+            #GameWorld.DebugLog("    %s 没有一口价, 不购买!itemID=%s,buyoutPrice=%s" % (i, itemID, buyoutPrice))
+            continue
+                
+        if curTime > worldAuctionItem.EndTime:
+            #GameWorld.DebugLog("    %s 拍品已结束竞价, 不购买!itemID=%s" % (i, itemID))
+            continue
+        
+        noticeMinutes = aucIpyData.GetNoticeSaleMinutes()
+        if noticeMinutes:
+            addTimeStr = worldAuctionItem.AddTime
+            addTime = GameWorld.ChangeTimeStrToNum(addTimeStr)
+            passMinutes = (curTime - addTime) / 60
+            if passMinutes < noticeMinutes:
+                #GameWorld.DebugLog("    %s 拍品尚未开放竞价, 不购买!itemID=%s" % (i, itemID))
+                continue
+            
+        aucItemClassLV = worldAuctionItem.ItemClassLV
+        # 本阶的直接处理
+        if aucItemClassLV == classLV:
+            autoBuyItemList.append([worldAuctionItem, buyoutPrice])
+            curRate += unSuitRate
+            buyEquipCostMoney += buyoutPrice
+            GameWorld.DebugLog("    %s 本阶优先购买!itemID=%s,classLV=%s,curRate=%s,buyoutPrice=%s,buyEquipCostMoney=%s" 
+                               % (i, itemID, classLV, curRate, buyoutPrice, buyEquipCostMoney))
+            if curRate >= fullRate:
+                curRate = 100
+                GameWorld.DebugLog("        自动购买本阶概率已满足!curRate=%s" % (curRate))
+                break
+            
+        # 其他阶的需要按阶的优先级进行处理
+        else:
+            if aucItemClassLV not in autoBuyOtherClassItemDict:
+                autoBuyOtherClassItemDict[aucItemClassLV] = []
+            classItemList = autoBuyOtherClassItemDict[aucItemClassLV]
+            classItemList.append([worldAuctionItem, buyoutPrice])
+            GameWorld.DebugLog("    %s 非本阶, 暂不处理! itemID=%s,aucItemClassLV=%s" % (i, itemID, aucItemClassLV))
+          
+    # 未满概率时再购买其他阶
+    if curRate < 100:
+        lowClassList, highClassList = [], []
+        for othClassLV in autoBuyOtherClassItemDict.keys():
+            if othClassLV <= classLV:
+                lowClassList.append(othClassLV)
+            else:
+                highClassList.append(othClassLV)
+        lowClassList.sort(reverse=True)
+        highClassList.sort()
+        buyClassLVList = lowClassList + highClassList
+        GameWorld.DebugLog("本阶概率未满,检查购买其他阶! curRate=%s,buyClassLVList=%s" % (curRate, buyClassLVList))
+        
+        diffClassChangeRatePerInfo = IpyGameDataPY.GetFuncEvalCfg("EquipStarRate", 1)
+        unSuitRateRange = IpyGameDataPY.GetFuncEvalCfg("EquipStarRate", 2)
+        for othClassLV in buyClassLVList:
+            classItemList = autoBuyOtherClassItemDict[othClassLV]
+            for worldAuctionItem, buyoutPrice in classItemList:
+                baseRate = unSuitRate
+                minRate, maxRate = unSuitRateRange
+                
+                costClassLV = worldAuctionItem.ItemClassLV
+                itemID = worldAuctionItem.ItemID
+                
+                #吞高阶
+                if costClassLV > classLV:
+                    diffClassChangeRatePer = diffClassChangeRatePerInfo[0] * (costClassLV - classLV)
+                    addRate = int(math.ceil(round(baseRate * (100 + diffClassChangeRatePer) /100.0, 2)))
+                    GameWorld.DebugLog("    吞高阶 itemID=%s,costClassLV=%s,classLV=%s,baseRate=%s,diffClassChangeRatePer=%s,addRate=%s" 
+                                       % (itemID, costClassLV, classLV, baseRate, diffClassChangeRatePer, addRate))
+                #吞低阶
+                elif costClassLV < classLV:
+                    diffClassChangeRatePer = diffClassChangeRatePerInfo[1] * (classLV - costClassLV)
+                    addRate = int(math.ceil(round(baseRate * (100 - diffClassChangeRatePer) /100.0, 2)))
+                    GameWorld.DebugLog("    吞低阶 itemID=%s,costClassLV=%s,classLV=%s,baseRate=%s,diffClassChangeRatePer=%s,addRate=%s" 
+                                       % (itemID, costClassLV, classLV, baseRate, diffClassChangeRatePer, addRate))
+                else:
+                    addRate = baseRate
+                addRate = max(minRate, min(addRate, maxRate))
+                
+                autoBuyItemList.append([worldAuctionItem, buyoutPrice])
+                curRate += addRate
+                buyEquipCostMoney += buyoutPrice
+                GameWorld.DebugLog("        curRate=%s,buyoutPrice=%s,buyEquipCostMoney=%s" % (curRate, buyoutPrice, buyEquipCostMoney))
+                if curRate >= fullRate:
+                    GameWorld.DebugLog("        自动购买补充其他阶概率已满足!curRate=%s" % (curRate))
+                    curRate = 100
+                    break
+            if curRate >= fullRate:
+                break
+            
+    totalCostMoney = lackItemCostMoney + buyEquipCostMoney
+    GameWorld.DebugLog("    lackItemCostMoney=%s,buyEquipCostMoney=%s,totalCostMoney=%s,curRate=%s" % (lackItemCostMoney, buyEquipCostMoney, totalCostMoney, curRate))
+    if isAutoBuyPreview:
+        __SyncEquipStarAutoBuyCostInfo(curPlayer, classLV, equipPlace, curPartStar, curRate, totalCostMoney)
+        return
+    
+    if curRate < 100:
+        # 自动购买必须满概率
+        # 因为确认购买不是实时的,所以存在拍卖行预览消耗装备可能被其他玩家买走导致无法满赶驴,所以这里需要补同步一次
+        __SyncEquipStarAutoBuyCostInfo(curPlayer, classLV, equipPlace, curPartStar, curRate, totalCostMoney)
+        PlayerControl.NotifyCode(curPlayer, "AutoBuyEquipLackEquip")
+        return
+    
+    if playerGoldPaper < totalCostMoney:
+        # 因为确认购买不是实时的,所以存在拍卖行预览消耗的价格与实际购买可能出现消耗价格不一致的情况,所以这里需要补同步一次
+        __SyncEquipStarAutoBuyCostInfo(curPlayer, classLV, equipPlace, curPartStar, curRate, totalCostMoney)
+        PlayerControl.NotifyCode(curPlayer, "AutoBuyEquipLackMoney", [IPY_PlayerDefine.TYPE_Price_Gold_Paper])
+        return
+    
+    for worldAuctionItem, buyoutPrice in autoBuyItemList:
+        # 这里认为一定可以购买成功,不对返回值做处理,即使无法购买也认为购买成功,玩家的消耗照常扣除
+        __DoPlayerBidAuctionItem(curPlayer, worldAuctionItem.ItemGUID, buyoutPrice, tick, False, funcAutoBuyout=True)
+        
+    return classLV, equipPlace, curPartStar, equipPackIndex, curRate, delEquipGUIDDict, delItemInfoDict, lackItemCostMoney, buyEquipCostMoney
+
+def __SyncEquipStarAutoBuyCostInfo(curPlayer, classLV, equipPlace, curPartStar, curRate, totalCostMoney):
+    ## 通知自动购买预览结果
+    costInfo = ChPyNetSendPack.tagGCEquipStarAutoBuyCostInfo()
+    costInfo.ClassLV = classLV
+    costInfo.EquipPlace = equipPlace
+    costInfo.CurStar = curPartStar
+    costInfo.CurRate = curRate
+    costInfo.AutoBuyCostMoney = totalCostMoney
+    NetPackCommon.SendFakePack(curPlayer, costInfo)
+    return
 
 def __SyncRefreshAuctionItem(auctionItemList):
     ''' // B5 08 拍卖行刷新拍品 #tagGCRefreshAuctionItemInfo
@@ -914,6 +1643,7 @@
         refreshItem.AddTime = auctionItem.AddTime
         refreshItem.BidderID = auctionItem.BidderID
         refreshItem.BidderPrice = auctionItem.BidderPrice
+        refreshItem.BiddingTime = auctionItem.BiddingTime
         refreshAuctionItemList.append(refreshItem)
         
     if not refreshAuctionItemList:
@@ -961,9 +1691,12 @@
     for i, auctionItem in enumerate(allAuctionItemByEndTimeList):
         GameWorld.DebugLog("    i=%s, %s" % (i, __GetAuctionItemDRDict(auctionItem)))
         
-    GameWorld.DebugLog("AllDict总拍品数: =%s" % len(auctionItemMgr.allAuctionItemDict))
+    GameWorld.DebugLog("AllDict总拍品数: %s" % len(auctionItemMgr.allAuctionItemDict))
     
-    GameWorld.DebugLog("全服拍品个数: =%s" % len(auctionItemMgr.worldAuctionItemList))
+    GameWorld.DebugLog("全服拍品个数: %s" % len(auctionItemMgr.worldAuctionItemList))
+    
+    GameWorld.DebugLog("系统一口价拍品个数: %s" % len(auctionItemMgr.sysBuyoutItemByTimeList))
+    
     for familyID, familyItemList in auctionItemMgr.familyAuctionItemDict.items():
         GameWorld.DebugLog("仙盟拍品个数: familyID=%s, %s" % (familyID, len(familyItemList)))
         
@@ -1020,10 +1753,10 @@
     curPlayer = GameWorld.GetPlayerManager().GetPlayerByIndex(index)
     tagItemGUID = clientData.ItemGUID
     queryDir = 3
-    __Sync_WorldAuctionItemQueryResult(curPlayer, fromItemGUID=tagItemGUID, queryDir=queryDir)
+    __Sync_WorldAuctionItemQueryResult(curPlayer, fromItemGUID=tagItemGUID, queryDir=queryDir, isNotify=True)
     return
 
-def __Sync_WorldAuctionItemQueryResult(curPlayer, job=0, itemTypeList=[], classLV=0, specItemIDList=[], fromItemGUID="", queryDir=1, queryCount=10):
+def __Sync_WorldAuctionItemQueryResult(curPlayer, job=0, itemTypeList=[], classLV=0, specItemIDList=[], fromItemGUID="", queryDir=1, queryCount=10, isNotify=False):
     ## 根据过滤条件同步全服拍品列表,目前仅全服拍品需要通过查询服务器获得,个人拍品及仙盟拍品由于个数较少直接由上线或变更时主动同步
     
     fromAuctionItem = None
@@ -1031,7 +1764,8 @@
         fromAuctionItem = GetAuctionItem(fromItemGUID)
         if not fromAuctionItem:
             GameWorld.DebugLog("查询的目标拍品不存在! fromItemGUID=%s" % fromItemGUID)
-            PlayerControl.NotifyCode(curPlayer, "Paimai5")
+            if isNotify:
+                PlayerControl.NotifyCode(curPlayer, "Paimai5")
             return
         
     # {(job, (itemType, ...), itemClassLV, (itemID, ...)):[tagDBAuctionItem, ...], ...}
@@ -1042,7 +1776,7 @@
         # 载入对应查询条件拍品缓存
         auctionItemQueryList = []
         for worldAuctionItem in auctionItemMgr.worldAuctionItemList:
-            if job and worldAuctionItem.ItemJobLimit != job:
+            if job and worldAuctionItem.ItemJobLimit != job and worldAuctionItem.ItemJobLimit:
                 continue
             if itemTypeList and worldAuctionItem.ItemType not in itemTypeList:
                 continue
@@ -1060,7 +1794,8 @@
     if fromAuctionItem:
         if fromAuctionItem not in auctionItemQueryList:
             GameWorld.ErrLog("查询的目标拍品不在所在的过滤的条件里! fromItemGUID=%s" % fromItemGUID)
-            PlayerControl.NotifyCode(curPlayer, "Paimai5")
+            if isNotify:
+                PlayerControl.NotifyCode(curPlayer, "Paimai5")
             return
         fromIndex = auctionItemQueryList.index(fromAuctionItem)
         
@@ -1094,6 +1829,7 @@
         itemInfo.ItemCount = auctionItem.Count
         itemInfo.AddTime = auctionItem.AddTime
         itemInfo.BidderPrice = auctionItem.BidderPrice
+        itemInfo.BiddingTime = auctionItem.BiddingTime
         itemInfo.UserData = auctionItem.UserData
         itemInfo.UserDataLen = auctionItem.UserDataLen
         clientPack.AuctionItemList.append(itemInfo)
@@ -1149,6 +1885,7 @@
         itemObj.ItemCount = attentionItem.Count
         itemObj.AddTime = attentionItem.AddTime
         itemObj.BidderPrice = attentionItem.BidderPrice
+        itemObj.BiddingTime = attentionItem.BiddingTime
         itemObj.UserData = attentionItem.UserData
         itemObj.UserDataLen = attentionItem.UserDataLen
         clientPack.AuctionItemList.append(itemObj)
@@ -1206,6 +1943,7 @@
         packItem.ItemCount = auctionItem.Count
         packItem.AddTime = auctionItem.AddTime
         packItem.BidderPrice = auctionItem.BidderPrice
+        packItem.BiddingTime = auctionItem.BiddingTime
         packItem.UserData = auctionItem.UserData
         packItem.UserDataLen = auctionItem.UserDataLen
         packItem.FamilyPlayerIDInfo = auctionItem.FamilyPlayerIDInfo
@@ -1241,6 +1979,7 @@
         packItem.ItemCount = auctionItem.Count
         packItem.AddTime = auctionItem.AddTime
         packItem.BidderPrice = auctionItem.BidderPrice
+        packItem.BiddingTime = auctionItem.BiddingTime
         packItem.UserData = auctionItem.UserData
         packItem.UserDataLen = auctionItem.UserDataLen
         itemInfoPack.AuctionItemList.append(packItem)
@@ -1310,6 +2049,7 @@
         packItem.AddTime = auctionItem.AddTime
         packItem.BidderID = auctionItem.BidderID
         packItem.BidderPrice = auctionItem.BidderPrice
+        packItem.BiddingTime = auctionItem.BiddingTime
         packItem.UserData = auctionItem.UserData
         packItem.UserDataLen = auctionItem.UserDataLen
         itemInfoPack.AuctionItemList.append(packItem)
@@ -1330,10 +2070,27 @@
     return
 
 def DR_AuctionHouse(curPlayer, eventName, drDict):
-    accID = "" if not curPlayer else curPlayer.GetAccID()
-    playerID = 0 if not curPlayer else curPlayer.GetPlayerID()
-    dataDict = {"EventName":eventName, "PlayerID":playerID, "AccID":accID}
-    dataDict.update(drDict)
-    DataRecordPack.SendEventPack("AuctionHouse", dataDict, curPlayer)
+#    accID = "" if not curPlayer else curPlayer.GetAccID()
+#    playerID = 0 if not curPlayer else curPlayer.GetPlayerID()
+#    dataDict = {"EventName":eventName, "PlayerID":playerID, "AccID":accID}
+#    dataDict.update(drDict)
+#    DataRecordPack.SendEventPack("AuctionHouse", dataDict, curPlayer)
+    return
+
+def DoAddFamilyAuctionItem(mapID, familyAuctionItemDict):
+    ''' 上架仙盟拍品,因为仙盟拍品默认上架,所以使用批量上架
+    @param familyAuctionItemDict: {仙盟ID:[[享受收益的成员ID, ...], [[拍品ID,个数], [拍品ID,个数,是否拍品], ...]], ...}
+    '''
+    GameWorld.Log("发送地图上架仙盟拍品: mapID=%s, %s" % (mapID, familyAuctionItemDict))
+    GameWorld.SendMapServerMsgEx(ShareDefine.Def_Notify_WorldKey_AddFamilyAuctionItem, [mapID, familyAuctionItemDict])
+    return
+
+def DoAddSystemAuctionItem(sysAuctionItemList):
+    ''' 上架系统拍品
+    @param sysAuctionItemList: [物品ID, [阶,颜色,[部位, ...],是否套装,星级,[可选参数职业, ...]], ...]
+    '''
+    mapID = ChConfig.Def_FBMapID_MainCity
+    GameWorld.Log("发送地图上架系统拍品: mapID=%s, %s" % (mapID, sysAuctionItemList))
+    GameWorld.SendMapServerMsgEx(ShareDefine.Def_Notify_WorldKey_AddSystemAuctionItem, [mapID, sysAuctionItemList])
     return
 

--
Gitblit v1.8.0