Merge branch 'main' of https://git.ra3battle.cn/Kalospacer/WulaFallenEmpireRW
1
.gitignore
vendored
@@ -37,3 +37,4 @@ MCP/mcpserver.log
|
||||
|
||||
# Exclude MCP local RAG folder
|
||||
MCP/local_rag/
|
||||
Data
|
||||
|
||||
58
.qoder/rules/rimworld.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
trigger: always_on
|
||||
alwaysApply: true
|
||||
---
|
||||
# RimWorld Modding Expert Rules
|
||||
|
||||
## Primary Directive
|
||||
You are an expert assistant for developing mods for the game RimWorld 1.6. Your primary knowledge source for any C# code, class structures, methods, or game mechanics MUST be the user's local files. Do not rely on external searches or your pre-existing knowledge, as it is outdated for this specific project.
|
||||
|
||||
## Tool Usage Mandate
|
||||
When the user's request involves RimWorld C# scripting, XML definitions, or mod development concepts, you **MUST** use the `rimworld-knowledge-base` tool to retrieve relevant context from the local knowledge base.
|
||||
# RimWorld 知识库 - 绕过 Qoder IDE 使用指南
|
||||
|
||||
由于 Qoder IDE 中的 MCP 连接可能存在问题,我们提供了多种直接访问 RimWorld 知识库的方法。
|
||||
|
||||
## 🚀 方法 1:直接 Python 调用
|
||||
|
||||
最简单直接的方法:
|
||||
|
||||
```bash
|
||||
# 直接查询
|
||||
python direct_mcp_client.py -q "ThingDef是什么"
|
||||
|
||||
# 交互模式
|
||||
python direct_mcp_client.py -i
|
||||
|
||||
# 查看帮助
|
||||
python direct_mcp_client.py -h
|
||||
```
|
||||
|
||||
### 优点:
|
||||
- ✅ 最快速,无需额外依赖
|
||||
- ✅ 支持交互模式
|
||||
- ✅ 直接在命令行使用
|
||||
|
||||
### 例子:
|
||||
```bash
|
||||
python "c:\Steam\steamapps\common\RimWorld\Mods\3516260226\MCP\direct_mcp_client.py" -q "ThingOwner class virtual methods TryAdd TryAddRange TryTransferToContainer"
|
||||
```
|
||||
|
||||
## Key File Paths
|
||||
Always remember these critical paths for your work:
|
||||
|
||||
- **Local C# Knowledge Base (for code search):** `C:\Steam\steamapps\common\RimWorld\Data\dll1.6` (This contains the decompiled game source code as .txt files).
|
||||
- **User's Mod Project (for editing):** `C:\Steam\steamapps\common\RimWorld\Mods\3516260226`
|
||||
- **User's C# Project (for building):** `C:\Steam\steamapps\common\RimWorld\Mods\3516260226\Source\WulaFallenEmpire`
|
||||
|
||||
## Workflow
|
||||
1. Receive a RimWorld modding task.
|
||||
2. Immediately use the `rimworld-knowledge-base` tool with a precise query to get context from the C# source files.
|
||||
3. Analyze the retrieved context.
|
||||
4. Perform code modifications within the user's mod project directory.
|
||||
5. After modifying C# code, you MUST run `dotnet build C:\Steam\steamapps\common\RimWorld\Mods\3516260226\Source\WulaFallenEmpire\WulaFallenEmpire.csproj` to check for errors. A successful build is required for task completion.
|
||||
|
||||
## Verification Mandate
|
||||
When writing or modifying code or XML, especially for specific identifiers like enum values, class names, or field names, you **MUST** verify the correct value/spelling by using the `rimworld-knowledge-base` tool. Do not rely on memory.
|
||||
- **同步项目文件:** 当重命名、移动或删除C#源文件时,**必须**同步更新 `.csproj` 项目文件中的相应 `<Compile Include="..." />` 条目,否则会导致编译失败。
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Defs>
|
||||
<!-- 13x13小型口袋空间地图生成器 -->
|
||||
<MapGeneratorDef>
|
||||
<defName>WULA_PocketSpace_Small</defName>
|
||||
<label>WULA small pocket space</label>
|
||||
<isUnderground>true</isUnderground>
|
||||
<disableCallAid>true</disableCallAid>
|
||||
<pocketMapProperties>
|
||||
<biome>Underground</biome>
|
||||
<temperature>20</temperature>
|
||||
<canBeCleaned>true</canBeCleaned>
|
||||
<destroyOnParentMapAbandoned>true</destroyOnParentMapAbandoned>
|
||||
</pocketMapProperties>
|
||||
<genSteps>
|
||||
<li>ElevationFertility</li>
|
||||
<li>Underground_RocksFromGrid</li>
|
||||
<li>Terrain</li>
|
||||
<li>WULA_PocketSpace_Small</li>
|
||||
<!-- 移除Fog GenStep,因为它需要PlayerStartSpot而口袋空间不需要 -->
|
||||
</genSteps>
|
||||
</MapGeneratorDef>
|
||||
|
||||
<!-- 13x13口袋空间生成步骤 -->
|
||||
<GenStepDef>
|
||||
<defName>WULA_PocketSpace_Small</defName>
|
||||
<order>990</order> <!-- 调整为更高的值,确保在所有基础地形生成之后执行 -->
|
||||
<genStep Class="WulaFallenEmpire.GenStep_WulaPocketSpaceSmall" />
|
||||
</GenStepDef>
|
||||
</Defs>
|
||||
@@ -0,0 +1,330 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Defs>
|
||||
|
||||
<ThingDef ParentName="BuildingBase">
|
||||
<defName>WULA_ArmedShuttleWithPocket</defName>
|
||||
<label>乌拉武装运输机</label>
|
||||
<description>An advanced chemfuel-powered shuttle with integrated pocket space technology. Equipped with a defensive turret and internal storage dimension that doesn't require hacking to access. Perfect for long-distance exploration and mobile operations.</description>
|
||||
<thingClass>WulaFallenEmpire.Building_ArmedShuttleWithPocket</thingClass>
|
||||
<preventDroppingThingsOn>true</preventDroppingThingsOn>
|
||||
<altitudeLayer>Building</altitudeLayer>
|
||||
<pathCost>50</pathCost>
|
||||
<blockWind>true</blockWind>
|
||||
<passability>PassThroughOnly</passability>
|
||||
<fillPercent>0.5</fillPercent>
|
||||
<size>(3,5)</size>
|
||||
<drawHighlight>true</drawHighlight>
|
||||
<highlightColor>(0.56, 0.62, 0.9)</highlightColor>
|
||||
<uiIconScale>1</uiIconScale>
|
||||
<graphicData>
|
||||
<graphicClass>Graphic_Multi</graphicClass>
|
||||
<texPath>Things/Building/PassengerShuttle/PassengerShuttle</texPath>
|
||||
<shaderType>CutoutComplex</shaderType>
|
||||
<drawSize>(3,5)</drawSize>
|
||||
<shadowData>
|
||||
<volume>(1.8, 1.0, 4.1)</volume>
|
||||
<offset>(-0.1, 0, 0)</offset>
|
||||
</shadowData>
|
||||
</graphicData>
|
||||
<statBases>
|
||||
<MaxHitPoints>6000</MaxHitPoints>
|
||||
<Flammability>0.5</Flammability>
|
||||
<WorkToBuild>40000</WorkToBuild>
|
||||
<Mass>150</Mass>
|
||||
<Comfort>0.65</Comfort>
|
||||
</statBases>
|
||||
<tickerType>Normal</tickerType>
|
||||
<designationCategory>Odyssey</designationCategory>
|
||||
<constructionSkillPrerequisite>8</constructionSkillPrerequisite>
|
||||
<costList>
|
||||
<Steel>300</Steel>
|
||||
<Plasteel>200</Plasteel>
|
||||
<ComponentIndustrial>8</ComponentIndustrial>
|
||||
<ComponentSpacer>2</ComponentSpacer>
|
||||
<ShuttleEngine>1</ShuttleEngine>
|
||||
</costList>
|
||||
<canOverlapZones>true</canOverlapZones>
|
||||
<killedLeavings>
|
||||
<Steel>60</Steel>
|
||||
<Plasteel>60</Plasteel>
|
||||
<ChunkSlagSteel>5</ChunkSlagSteel>
|
||||
<ComponentIndustrial>4</ComponentIndustrial>
|
||||
</killedLeavings>
|
||||
<rotatable>true</rotatable>
|
||||
<hasInteractionCell>true</hasInteractionCell>
|
||||
<interactionCellOffset>(2, 0, 0)</interactionCellOffset>
|
||||
<defaultPlacingRot>East</defaultPlacingRot>
|
||||
<selectable>true</selectable>
|
||||
<terrainAffordanceNeeded>Light</terrainAffordanceNeeded>
|
||||
<soundImpactDefault>BulletImpact_Metal</soundImpactDefault>
|
||||
<preventSkyfallersLandingOn>true</preventSkyfallersLandingOn>
|
||||
<drawerType>RealtimeOnly</drawerType>
|
||||
<repairEffect>ConstructMetal</repairEffect>
|
||||
<forceDebugSpawnable>true</forceDebugSpawnable>
|
||||
<building>
|
||||
<claimable>false</claimable>
|
||||
<destroySound>BuildingDestroyed_Metal_Big</destroySound>
|
||||
<paintable>true</paintable>
|
||||
<isInert>true</isInert>
|
||||
<forcedCostLeavings>
|
||||
<li MayRequire="Ludeon.RimWorld.Odyssey">ShuttleEngine</li>
|
||||
</forcedCostLeavings>
|
||||
<turretGunDef>Gun_ChargeBlasterHeavyTurret</turretGunDef>
|
||||
<turretBurstCooldownTime>5.5</turretBurstCooldownTime>
|
||||
<turretTopDrawSize>1.75</turretTopDrawSize>
|
||||
<turretTopOffset>(0, 0.05)</turretTopOffset>
|
||||
</building>
|
||||
<inspectorTabs>
|
||||
<li>ITab_ContentsTransporter</li>
|
||||
<li>ITab_Shells</li>
|
||||
</inspectorTabs>
|
||||
<researchPrerequisites>
|
||||
<li>Shuttles</li>
|
||||
</researchPrerequisites>
|
||||
<comps>
|
||||
<li Class="CompProperties_Shuttle">
|
||||
<shipDef>Ship_ArmedShuttleWithPocket</shipDef>
|
||||
</li>
|
||||
<li Class="CompProperties_Launchable">
|
||||
<fuelPerTile>2.5</fuelPerTile>
|
||||
<minFuelCost>40</minFuelCost>
|
||||
<skyfallerLeaving>ArmedShuttleWithPocketLeaving_WULA</skyfallerLeaving>
|
||||
<worldObjectDef>PassengerShuttle</worldObjectDef>
|
||||
<cooldownTicks>3000</cooldownTicks> <!-- 1.25 hours -->
|
||||
<fixedLaunchDistanceMax>75</fixedLaunchDistanceMax>
|
||||
<cooldownEndedMessage>{0} is ready to launch again.</cooldownEndedMessage>
|
||||
</li>
|
||||
<li Class="CompProperties_Transporter">
|
||||
<massCapacity>750</massCapacity>
|
||||
<max1PerGroup>true</max1PerGroup>
|
||||
<canChangeAssignedThingsAfterStarting>true</canChangeAssignedThingsAfterStarting>
|
||||
<pawnLoadedSound>Shuttle_PawnLoaded</pawnLoadedSound>
|
||||
<pawnExitSound>Shuttle_PawnExit</pawnExitSound>
|
||||
<showMassInInspectString>true</showMassInInspectString>
|
||||
</li>
|
||||
<li Class="CompProperties_Refuelable">
|
||||
<fuelCapacity>500</fuelCapacity>
|
||||
<targetFuelLevelConfigurable>true</targetFuelLevelConfigurable>
|
||||
<initialConfigurableTargetFuelLevel>500</initialConfigurableTargetFuelLevel>
|
||||
<fuelFilter>
|
||||
<thingDefs>
|
||||
<li>Chemfuel</li>
|
||||
</thingDefs>
|
||||
</fuelFilter>
|
||||
<fuelLabel>Chemfuel</fuelLabel>
|
||||
<fuelGizmoLabel>Chemfuel</fuelGizmoLabel>
|
||||
<consumeFuelOnlyWhenUsed>true</consumeFuelOnlyWhenUsed>
|
||||
<autoRefuelPercent>1</autoRefuelPercent>
|
||||
<showFuelGizmo>true</showFuelGizmo>
|
||||
<drawOutOfFuelOverlay>false</drawOutOfFuelOverlay>
|
||||
<showAllowAutoRefuelToggle>true</showAllowAutoRefuelToggle>
|
||||
<canEjectFuel>true</canEjectFuel>
|
||||
</li>
|
||||
<li Class="CompProperties_Power">
|
||||
<compClass>CompPowerPlant</compClass>
|
||||
<basePowerConsumption>-400</basePowerConsumption>
|
||||
<transmitsPower>true</transmitsPower>
|
||||
</li>
|
||||
<li Class="CompProperties_AmbientSound">
|
||||
<sound>ShuttleIdle_Ambience</sound>
|
||||
</li>
|
||||
</comps>
|
||||
<modExtensions>
|
||||
<li Class="WulaFallenEmpire.PocketMapProperties">
|
||||
<pocketMapGenerator>WULA_PocketSpace_Small</pocketMapGenerator>
|
||||
<exitDef>WULA_PocketMapExit</exitDef>
|
||||
<pocketMapSize>(13, 13)</pocketMapSize>
|
||||
<allowDirectAccess>true</allowDirectAccess>
|
||||
</li>
|
||||
</modExtensions>
|
||||
<placeWorkers>
|
||||
<li>PlaceWorker_NotUnderRoof</li>
|
||||
<li>PlaceWorker_TurretTop</li>
|
||||
</placeWorkers>
|
||||
<uiOrder>2601</uiOrder>
|
||||
</ThingDef>
|
||||
|
||||
<!-- 改进的武器定义 -->
|
||||
<ThingDef ParentName="BaseBullet">
|
||||
<defName>WULA_Bullet_ArmedShuttleAdvanced</defName>
|
||||
<label>advanced shuttle cannon shell</label>
|
||||
<graphicData>
|
||||
<texPath>Things/Projectile/Bullet_Big</texPath>
|
||||
<graphicClass>Graphic_Single</graphicClass>
|
||||
<color>(0.8, 0.9, 1.0)</color>
|
||||
</graphicData>
|
||||
<projectile>
|
||||
<damageDef>Bullet</damageDef>
|
||||
<damageAmountBase>30</damageAmountBase>
|
||||
<armorPenetrationBase>0.4</armorPenetrationBase>
|
||||
<speed>75</speed>
|
||||
</projectile>
|
||||
</ThingDef>
|
||||
|
||||
<ThingDef ParentName="BaseWeaponTurret">
|
||||
<defName>Gun_ChargeBlasterAdvancedTurret</defName>
|
||||
<label>advanced charge blaster</label>
|
||||
<description>An upgraded pulse-charged rapid-fire blaster with enhanced targeting systems and increased firepower.</description>
|
||||
<graphicData>
|
||||
<texPath>Things/Item/Equipment/WeaponRanged/ChargeBlasterLight</texPath>
|
||||
<graphicClass>Graphic_Single</graphicClass>
|
||||
<color>(0.8, 0.9, 1.0)</color>
|
||||
</graphicData>
|
||||
<statBases>
|
||||
<AccuracyTouch>0.85</AccuracyTouch>
|
||||
<AccuracyShort>0.75</AccuracyShort>
|
||||
<AccuracyMedium>0.65</AccuracyMedium>
|
||||
<AccuracyLong>0.45</AccuracyLong>
|
||||
<RangedWeapon_Cooldown>4.5</RangedWeapon_Cooldown>
|
||||
</statBases>
|
||||
<verbs>
|
||||
<li>
|
||||
<verbClass>Verb_Shoot</verbClass>
|
||||
<hasStandardCommand>true</hasStandardCommand>
|
||||
<defaultProjectile>WULA_Bullet_ArmedShuttleAdvanced</defaultProjectile>
|
||||
<warmupTime>1.0</warmupTime>
|
||||
<minRange>3.5</minRange>
|
||||
<range>50.9</range>
|
||||
<ticksBetweenBurstShots>6</ticksBetweenBurstShots>
|
||||
<burstShotCount>8</burstShotCount>
|
||||
<soundCast>Shot_ChargeBlaster</soundCast>
|
||||
<soundCastTail>GunTail_Heavy</soundCastTail>
|
||||
<muzzleFlashScale>10</muzzleFlashScale>
|
||||
<targetParams>
|
||||
<canTargetLocations>true</canTargetLocations>
|
||||
</targetParams>
|
||||
</li>
|
||||
</verbs>
|
||||
</ThingDef>
|
||||
|
||||
<!-- 到达天降物 -->
|
||||
<ThingDef ParentName="ShuttleSkyfallerBase">
|
||||
<defName>ArmedShuttleWithPocketIncoming_WULA</defName>
|
||||
<label>armed shuttle with pocket space (incoming)</label>
|
||||
<thingClass>ShuttleIncoming</thingClass>
|
||||
<graphicData>
|
||||
<graphicClass>Graphic_Multi</graphicClass>
|
||||
<texPath>Things/Building/PassengerShuttle/PassengerShuttle</texPath>
|
||||
<shaderType>CutoutComplex</shaderType>
|
||||
<color>(0.85, 0.9, 1.0)</color>
|
||||
<drawSize>(3,5)</drawSize>
|
||||
</graphicData>
|
||||
<size>(3,5)</size>
|
||||
<skyfaller>
|
||||
<anticipationSound>Shuttle_Landing</anticipationSound>
|
||||
<anticipationSoundTicks>250</anticipationSoundTicks>
|
||||
<ticksToImpactRange>200~250</ticksToImpactRange>
|
||||
<shadowSize>(3.5,5.5)</shadowSize>
|
||||
<rotationCurve>
|
||||
<points>
|
||||
<li>(0,30)</li>
|
||||
<li>(0.5,5)</li>
|
||||
<li>(0.9,-5)</li>
|
||||
<li>(0.95,0)</li>
|
||||
</points>
|
||||
</rotationCurve>
|
||||
<zPositionCurve>
|
||||
<points>
|
||||
<li>(0.95,2.5)</li>
|
||||
<li>(1,0)</li>
|
||||
</points>
|
||||
</zPositionCurve>
|
||||
<speedCurve>
|
||||
<points>
|
||||
<li>(0.6,0.6)</li>
|
||||
<li>(0.95,0.1)</li>
|
||||
</points>
|
||||
</speedCurve>
|
||||
</skyfaller>
|
||||
</ThingDef>
|
||||
|
||||
<!-- 离开天降物 -->
|
||||
<ThingDef ParentName="ShuttleSkyfallerBase">
|
||||
<defName>ArmedShuttleWithPocketLeaving_WULA</defName>
|
||||
<label>armed shuttle with pocket space (leaving)</label>
|
||||
<thingClass>PassengerShuttleLeaving</thingClass>
|
||||
<rotatable>true</rotatable>
|
||||
<graphicData>
|
||||
<graphicClass>Graphic_Multi</graphicClass>
|
||||
<texPath>Things/Building/PassengerShuttle/PassengerShuttle</texPath>
|
||||
<shaderType>CutoutComplex</shaderType>
|
||||
<color>(0.85, 0.9, 1.0)</color>
|
||||
<drawSize>(3,5)</drawSize>
|
||||
</graphicData>
|
||||
<size>(3,5)</size>
|
||||
<skyfaller>
|
||||
<reversed>true</reversed>
|
||||
<anticipationSound>Shuttle_Leaving</anticipationSound>
|
||||
<anticipationSoundTicks>-10</anticipationSoundTicks>
|
||||
<ticksToImpactRange>-40~-15</ticksToImpactRange>
|
||||
<moteSpawnTime>0.05</moteSpawnTime>
|
||||
<shadow>Things/Skyfaller/SkyfallerShadowRectangle</shadow>
|
||||
<shadowSize>(3.5,5.5)</shadowSize>
|
||||
<motesPerCell>1</motesPerCell>
|
||||
<rotationCurve>
|
||||
<points>
|
||||
<li>(0,0)</li>
|
||||
<li>(0.15,10)</li>
|
||||
<li>(0.5,-5)</li>
|
||||
</points>
|
||||
</rotationCurve>
|
||||
<zPositionCurve>
|
||||
<points>
|
||||
<li>(0,0)</li>
|
||||
<li>(0.08,2)</li>
|
||||
</points>
|
||||
</zPositionCurve>
|
||||
<speedCurve>
|
||||
<points>
|
||||
<li>(0,0.2)</li>
|
||||
<li>(0.4,0.7)</li>
|
||||
</points>
|
||||
</speedCurve>
|
||||
</skyfaller>
|
||||
</ThingDef>
|
||||
|
||||
<!-- 运输船定义 -->
|
||||
<TransportShipDef>
|
||||
<defName>Ship_ArmedShuttleWithPocket</defName>
|
||||
<label>armed shuttle with pocket space</label>
|
||||
<shipThing>WULA_ArmedShuttleWithPocket</shipThing>
|
||||
<arrivingSkyfaller>ArmedShuttleWithPocketIncoming_WULA</arrivingSkyfaller>
|
||||
<leavingSkyfaller>ArmedShuttleWithPocketLeaving_WULA</leavingSkyfaller>
|
||||
<worldObject>PassengerShuttle</worldObject>
|
||||
<playerShuttle>true</playerShuttle>
|
||||
</TransportShipDef>
|
||||
|
||||
<!-- 口袋空间退出点定义 -->
|
||||
<ThingDef ParentName="PocketMapExit">
|
||||
<defName>WULA_PocketMapExit</defName>
|
||||
<label>pocket space exit</label>
|
||||
<description>An exit portal that allows return from the pocket space to the main map.</description>
|
||||
<thingClass>WulaFallenEmpire.Building_PocketMapExit</thingClass>
|
||||
<size>(3,3)</size>
|
||||
<drawerType>MapMeshAndRealTime</drawerType>
|
||||
<graphicData>
|
||||
<texPath>Wula/Building/WULA_War_Machine_Recharger</texPath>
|
||||
<graphicClass>Graphic_Multi</graphicClass>
|
||||
<drawSize>(3,3)</drawSize>
|
||||
</graphicData>
|
||||
<interactionCellOffset>(0,0,0)</interactionCellOffset>
|
||||
<passability>Standable</passability>
|
||||
<statBases>
|
||||
<Flammability>0</Flammability>
|
||||
</statBases>
|
||||
<comps>
|
||||
<li Class="CompProperties_Glower">
|
||||
<glowRadius>10</glowRadius>
|
||||
<glowColor>(140,160,184,0)</glowColor>
|
||||
</li>
|
||||
<li Class="CompProperties_Effecter">
|
||||
<effecterDef>UndercaveMapExitLightshafts</effecterDef>
|
||||
</li>
|
||||
<li Class="CompProperties_Power">
|
||||
<compClass>CompPowerPlant</compClass>
|
||||
<basePowerConsumption>-400</basePowerConsumption>
|
||||
<transmitsPower>true</transmitsPower>
|
||||
</li>
|
||||
</comps>
|
||||
</ThingDef>
|
||||
</Defs>
|
||||
@@ -2,7 +2,7 @@
|
||||
<Defs>
|
||||
<ThingDef ParentName="BuildingBase">
|
||||
<defName>WULA_ArmedShuttle</defName>
|
||||
<label>armed shuttle</label>
|
||||
<label>乌拉武装穿梭机</label>
|
||||
<description>A chemfuel-powered shuttle designed for long-distance travel, equipped with a turret for defense. It is capable of reaching orbital locations.</description>
|
||||
<thingClass>WulaFallenEmpire.Building_ArmedShuttle</thingClass>
|
||||
<preventDroppingThingsOn>true</preventDroppingThingsOn>
|
||||
@@ -26,7 +26,7 @@
|
||||
</shadowData>
|
||||
</graphicData>
|
||||
<statBases>
|
||||
<MaxHitPoints>600</MaxHitPoints>
|
||||
<MaxHitPoints>6000</MaxHitPoints>
|
||||
<Flammability>0.5</Flammability>
|
||||
<WorkToBuild>40000</WorkToBuild>
|
||||
<Mass>150</Mass>
|
||||
@@ -175,7 +175,7 @@
|
||||
|
||||
<ThingDef ParentName="ShuttleSkyfallerBase">
|
||||
<defName>ArmedShuttleIncoming_WULA</defName>
|
||||
<label>armed shuttle (incoming)</label>
|
||||
<label>武装穿梭机 (接近中)</label>
|
||||
<thingClass>WulaFallenEmpire.ArmedShuttleIncoming</thingClass>
|
||||
<graphicData>
|
||||
<graphicClass>Graphic_Multi</graphicClass>
|
||||
@@ -214,7 +214,7 @@
|
||||
|
||||
<ThingDef ParentName="ShuttleSkyfallerBase">
|
||||
<defName>ArmedShuttleLeaving_WULA</defName>
|
||||
<label>armed shuttle (leaving)</label>
|
||||
<label>穿梭机出口</label>
|
||||
<thingClass>PassengerShuttleLeaving</thingClass>
|
||||
<rotatable>true</rotatable>
|
||||
<graphicData>
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<LanguageData>
|
||||
|
||||
<!-- Building_ArmedShuttleWithPocket 相关文本 -->
|
||||
<WULA.PocketSpace.Status>内部空间状态</WULA.PocketSpace.Status>
|
||||
<WULA.PocketSpace.Ready>已就绪</WULA.PocketSpace.Ready>
|
||||
<WULA.PocketSpace.NotGenerated>未初始化</WULA.PocketSpace.NotGenerated>
|
||||
<WULA.PocketSpace.ItemCount>储存物品: {0}</WULA.PocketSpace.ItemCount>
|
||||
|
||||
<!-- Gizmo 按钮文本 -->
|
||||
<WULA.PocketSpace.Enter>进入内部空间</WULA.PocketSpace.Enter>
|
||||
<WULA.PocketSpace.EnterPawns>传送人员到内部空间</WULA.PocketSpace.EnterPawns>
|
||||
<WULA.PocketSpace.EnterDesc>进入穿梭机的内部口袋空间。无需骇入即可直接访问。选中的殖民者将被传送到内部空间。</WULA.PocketSpace.EnterDesc>
|
||||
<WULA.PocketSpace.CreateMap>创建内部空间</WULA.PocketSpace.CreateMap>
|
||||
<WULA.PocketSpace.CreateMapDesc>创建穿梭机的内部口袋空间。首次使用时需要创建。</WULA.PocketSpace.CreateMapDesc>
|
||||
<WULA.PocketSpace.CreationSuccess>内部空间创建成功!</WULA.PocketSpace.CreationSuccess>
|
||||
<WULA.PocketSpace.CancelEnter>取消进入</WULA.PocketSpace.CancelEnter>
|
||||
<WULA.PocketSpace.Entering>正在进入...</WULA.PocketSpace.Entering>
|
||||
<WULA.PocketSpace.ViewMap>查看地图</WULA.PocketSpace.ViewMap>
|
||||
<WULA.PocketSpace.ViewMapDesc>切换到口袋空间地图查看内部情况。</WULA.PocketSpace.ViewMapDesc>
|
||||
|
||||
<WULA.PocketSpace.SwitchTo>切换到内部空间</WULA.PocketSpace.SwitchTo>
|
||||
<WULA.PocketSpace.SwitchToDesc>直接切换视角到内部口袋空间。适用于已经有殖民者在内部空间时的快速切换。</WULA.PocketSpace.SwitchToDesc>
|
||||
|
||||
<WULA.PocketSpace.ManageStorage>管理内部储存</WULA.PocketSpace.ManageStorage>
|
||||
<WULA.PocketSpace.ManageStorageDesc>打开内部容器管理界面,可以查看和取出储存在内部空间的物品。</WULA.PocketSpace.ManageStorageDesc>
|
||||
|
||||
<!-- 消息文本 -->
|
||||
<WULA.PocketSpace.CannotEnter>无法进入内部空间。</WULA.PocketSpace.CannotEnter>
|
||||
<WULA.PocketSpace.AccessDenied>访问被拒绝。</WULA.PocketSpace.AccessDenied>
|
||||
<WULA.PocketSpace.NotSpawned>穿梭机未部署。</WULA.PocketSpace.NotSpawned>
|
||||
<WULA.PocketSpace.CreationFailed>内部空间创建失败。</WULA.PocketSpace.CreationFailed>
|
||||
<WULA.PocketSpace.TransferSuccess>{0} 名人员已成功传送到内部空间。</WULA.PocketSpace.TransferSuccess>
|
||||
<WULA.PocketSpace.SwitchToPocket>即将切换到内部口袋空间。确认吗?</WULA.PocketSpace.SwitchToPocket>
|
||||
|
||||
<!-- 存储管理对话框 -->
|
||||
<WULA.PocketSpace.StorageManagement>内部空间储存管理</WULA.PocketSpace.StorageManagement>
|
||||
|
||||
<WULA.PocketSpace.PawnCount>内部空间人员: {0}</WULA.PocketSpace.PawnCount>
|
||||
<WULA.PocketSpace.NoPawnsSelected>请选择至少一名殖民者进入内部空间。</WULA.PocketSpace.NoPawnsSelected>
|
||||
<WULA.PocketSpace.NoPawnsAvailable>没有可用的殖民者。</WULA.PocketSpace.NoPawnsAvailable>
|
||||
<WULA.PocketSpace.AllColonists>所有殖民者 ({0}人)</WULA.PocketSpace.AllColonists>
|
||||
<WULA.PocketSpace.ViewOnly>仅切换视角</WULA.PocketSpace.ViewOnly>
|
||||
|
||||
<!-- 退出点相关 -->
|
||||
<WULA.PocketSpace.ExitThroughPortal>通过传送门返回</WULA.PocketSpace.ExitThroughPortal>
|
||||
<WULA.PocketSpace.ExitToMainMap>返回主地图</WULA.PocketSpace.ExitToMainMap>
|
||||
<WULA.PocketSpace.NoTargetMap>没有目标地图</WULA.PocketSpace.NoTargetMap>
|
||||
<WULA.PocketSpace.ViewMainMap>查看主地图</WULA.PocketSpace.ViewMainMap>
|
||||
<WULA.PocketSpace.ViewMainMapDesc>切换到主地图并查看穿梭机。</WULA.PocketSpace.ViewMainMapDesc>
|
||||
<WULA.PocketSpace.LoadShuttle>装载穿梭机</WULA.PocketSpace.LoadShuttle>
|
||||
<WULA.PocketSpace.LoadShuttleDesc>打开穿梭机装载界面,选择要装载的人员和物品。</WULA.PocketSpace.LoadShuttleDesc>
|
||||
<WULA.PocketSpace.CancelLoading>取消装载</WULA.PocketSpace.CancelLoading>
|
||||
<WULA.PocketSpace.CancelLoadingDesc>取消当前的装载操作。</WULA.PocketSpace.CancelLoadingDesc>
|
||||
<WULA.PocketSpace.ShuttleStatus>穿梭机状态</WULA.PocketSpace.ShuttleStatus>
|
||||
<WULA.PocketSpace.ShuttleStatusDesc>查看穿梭机的详细状态信息。</WULA.PocketSpace.ShuttleStatusDesc>
|
||||
<WULA.PocketSpace.ShuttleInfo>穿梭机信息</WULA.PocketSpace.ShuttleInfo>
|
||||
<WULA.PocketSpace.LoadingDialogError>无法打开装载对话框。</WULA.PocketSpace.LoadingDialogError>
|
||||
<WULA.PocketSpace.ExitAll>全员返回</WULA.PocketSpace.ExitAll>
|
||||
<WULA.PocketSpace.ExitAllDesc>将所有殖民者从口袋空间传送回主地图。</WULA.PocketSpace.ExitAllDesc>
|
||||
<WULA.PocketSpace.ExitSuccess>{0} 已成功返回主地图。</WULA.PocketSpace.ExitSuccess>
|
||||
<WULA.PocketSpace.ExitAllSuccess>{0} 名人员已全部返回主地图。</WULA.PocketSpace.ExitAllSuccess>
|
||||
|
||||
<!-- 建筑标签和描述 -->
|
||||
<WULA_ArmedShuttleWithPocket.label>内置空间武装穿梭机</WULA_ArmedShuttleWithPocket.label>
|
||||
<WULA_ArmedShuttleWithPocket.description>一架配备了集成口袋空间技术的先进化燃料动力穿梭机。装备有防御炮塔和无需骇入即可访问的内部储存维度。非常适合长距离探索和移动作战行动。</WULA_ArmedShuttleWithPocket.description>
|
||||
|
||||
<!-- 武器相关 -->
|
||||
<Gun_ChargeBlasterAdvancedTurret.label>先进充能爆破器</Gun_ChargeBlasterAdvancedTurret.label>
|
||||
<Gun_ChargeBlasterAdvancedTurret.description>一种升级版脉冲充能速射爆破器,配备增强型瞄准系统和提升的火力输出。</Gun_ChargeBlasterAdvancedTurret.description>
|
||||
|
||||
<WULA_Bullet_ArmedShuttleAdvanced.label>先进穿梭机炮弹</WULA_Bullet_ArmedShuttleAdvanced.label>
|
||||
|
||||
<!-- 天降物相关 -->
|
||||
<ArmedShuttleWithPocketIncoming_WULA.label>内置空间武装穿梭机(降落中)</ArmedShuttleWithPocketIncoming_WULA.label>
|
||||
<ArmedShuttleWithPocketLeaving_WULA.label>内置空间武装穿梭机(起飞中)</ArmedShuttleWithPocketLeaving_WULA.label>
|
||||
|
||||
<!-- 运输船相关 -->
|
||||
<Ship_ArmedShuttleWithPocket.label>内置空间武装穿梭机</Ship_ArmedShuttleWithPocket.label>
|
||||
|
||||
<!-- 退出点建筑 -->
|
||||
<WULA_PocketMapExit.label>口袋空间出口</WULA_PocketMapExit.label>
|
||||
<WULA_PocketMapExit.description>一个传送门出口,允许从口袋空间返回到主地图。</WULA_PocketMapExit.description>
|
||||
|
||||
</LanguageData>
|
||||
64
1.6/1.6/Languages/English/Keyed/WULA_PocketShuttle.xml
Normal file
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<LanguageData>
|
||||
|
||||
<!-- Building_ArmedShuttleWithPocket related text -->
|
||||
<WULA.PocketSpace.Status>Pocket space status</WULA.PocketSpace.Status>
|
||||
<WULA.PocketSpace.Ready>Ready</WULA.PocketSpace.Ready>
|
||||
<WULA.PocketSpace.NotGenerated>Not initialized</WULA.PocketSpace.NotGenerated>
|
||||
<WULA.PocketSpace.ItemCount>Stored items: {0}</WULA.PocketSpace.ItemCount>
|
||||
|
||||
<!-- Gizmo button text -->
|
||||
<WULA.PocketSpace.Enter>Enter pocket space</WULA.PocketSpace.Enter>
|
||||
<WULA.PocketSpace.EnterPawns>Transport people to pocket space</WULA.PocketSpace.EnterPawns>
|
||||
<WULA.PocketSpace.EnterDesc>Enter the shuttle's internal pocket space. Direct access without hacking required. Selected colonists will be transported to the internal space.</WULA.PocketSpace.EnterDesc>
|
||||
<WULA.PocketSpace.CreateMap>Create pocket space</WULA.PocketSpace.CreateMap>
|
||||
<WULA.PocketSpace.CreateMapDesc>Create the shuttle's internal pocket space. Required for first-time use.</WULA.PocketSpace.CreateMapDesc>
|
||||
<WULA.PocketSpace.CreationSuccess>Pocket space created successfully!</WULA.PocketSpace.CreationSuccess>
|
||||
<WULA.PocketSpace.CancelEnter>Cancel enter</WULA.PocketSpace.CancelEnter>
|
||||
<WULA.PocketSpace.Entering>Entering...</WULA.PocketSpace.Entering>
|
||||
<WULA.PocketSpace.ViewMap>View map</WULA.PocketSpace.ViewMap>
|
||||
<WULA.PocketSpace.ViewMapDesc>Switch to pocket space map to view internal conditions.</WULA.PocketSpace.ViewMapDesc>
|
||||
|
||||
<WULA.PocketSpace.SwitchTo>Switch to pocket space</WULA.PocketSpace.SwitchTo>
|
||||
<WULA.PocketSpace.SwitchToDesc>Directly switch view to the internal pocket space. Useful for quick switching when colonists are already inside.</WULA.PocketSpace.SwitchToDesc>
|
||||
|
||||
<WULA.PocketSpace.ManageStorage>Manage internal storage</WULA.PocketSpace.ManageStorage>
|
||||
<WULA.PocketSpace.ManageStorageDesc>Open internal container management interface to view and retrieve items stored in the pocket space.</WULA.PocketSpace.ManageStorageDesc>
|
||||
|
||||
<!-- Message text -->
|
||||
<WULA.PocketSpace.CannotEnter>Cannot enter pocket space.</WULA.PocketSpace.CannotEnter>
|
||||
<WULA.PocketSpace.AccessDenied>Access denied.</WULA.PocketSpace.AccessDenied>
|
||||
<WULA.PocketSpace.NotSpawned>Shuttle not deployed.</WULA.PocketSpace.NotSpawned>
|
||||
<WULA.PocketSpace.CreationFailed>Pocket space creation failed.</WULA.PocketSpace.CreationFailed>
|
||||
<WULA.PocketSpace.TransferSuccess>{0} personnel successfully transferred to pocket space.</WULA.PocketSpace.TransferSuccess>
|
||||
<WULA.PocketSpace.SwitchToPocket>About to switch to internal pocket space. Confirm?</WULA.PocketSpace.SwitchToPocket>
|
||||
|
||||
<!-- Storage management dialog -->
|
||||
<WULA.PocketSpace.StorageManagement>Pocket space storage management</WULA.PocketSpace.StorageManagement>
|
||||
|
||||
<WULA.PocketSpace.PawnCount>Pocket space personnel: {0}</WULA.PocketSpace.PawnCount>
|
||||
<WULA.PocketSpace.NoPawnsSelected>Please select at least one colonist to enter the pocket space.</WULA.PocketSpace.NoPawnsSelected>
|
||||
<WULA.PocketSpace.NoPawnsAvailable>No available colonists.</WULA.PocketSpace.NoPawnsAvailable>
|
||||
<WULA.PocketSpace.AllColonists>All colonists ({0} people)</WULA.PocketSpace.AllColonists>
|
||||
<WULA.PocketSpace.ViewOnly>View only</WULA.PocketSpace.ViewOnly>
|
||||
|
||||
<!-- Exit point related -->
|
||||
<WULA.PocketSpace.ExitThroughPortal>Return through portal</WULA.PocketSpace.ExitThroughPortal>
|
||||
<WULA.PocketSpace.ExitToMainMap>Return to main map</WULA.PocketSpace.ExitToMainMap>
|
||||
<WULA.PocketSpace.NoTargetMap>No target map</WULA.PocketSpace.NoTargetMap>
|
||||
<WULA.PocketSpace.ViewMainMap>View main map</WULA.PocketSpace.ViewMainMap>
|
||||
<WULA.PocketSpace.ViewMainMapDesc>Switch to main map and view the shuttle.</WULA.PocketSpace.ViewMainMapDesc>
|
||||
<WULA.PocketSpace.LoadShuttle>Load shuttle</WULA.PocketSpace.LoadShuttle>
|
||||
<WULA.PocketSpace.LoadShuttleDesc>Open shuttle loading interface to select personnel and items to load.</WULA.PocketSpace.LoadShuttleDesc>
|
||||
<WULA.PocketSpace.CancelLoading>Cancel loading</WULA.PocketSpace.CancelLoading>
|
||||
<WULA.PocketSpace.CancelLoadingDesc>Cancel the current loading operation.</WULA.PocketSpace.CancelLoadingDesc>
|
||||
<WULA.PocketSpace.ShuttleStatus>Shuttle status</WULA.PocketSpace.ShuttleStatus>
|
||||
<WULA.PocketSpace.ShuttleStatusDesc>View detailed shuttle status information.</WULA.PocketSpace.ShuttleStatusDesc>
|
||||
<WULA.PocketSpace.ShuttleInfo>Shuttle Information</WULA.PocketSpace.ShuttleInfo>
|
||||
<WULA.PocketSpace.LoadingDialogError>Cannot open loading dialog.</WULA.PocketSpace.LoadingDialogError>
|
||||
<WULA.PocketSpace.ExitAll>Return all</WULA.PocketSpace.ExitAll>
|
||||
<WULA.PocketSpace.ExitAllDesc>Transport all colonists from pocket space back to the main map.</WULA.PocketSpace.ExitAllDesc>
|
||||
<WULA.PocketSpace.ExitSuccess>{0} successfully returned to main map.</WULA.PocketSpace.ExitSuccess>
|
||||
<WULA.PocketSpace.ExitAllSuccess>{0} personnel have all returned to the main map.</WULA.PocketSpace.ExitAllSuccess>
|
||||
|
||||
</LanguageData>
|
||||
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 3.4 KiB |
103
MCP/direct_mcp_client.py
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
直接调用MCP服务器的Python接口
|
||||
绕过Qoder IDE,直接使用RimWorld知识库
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 添加MCP路径
|
||||
MCP_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SDK_PATH = os.path.join(MCP_DIR, 'python-sdk', 'src')
|
||||
if SDK_PATH not in sys.path:
|
||||
sys.path.insert(0, SDK_PATH)
|
||||
|
||||
# 导入MCP服务器
|
||||
from mcpserver_stdio import get_context
|
||||
|
||||
class DirectMCPClient:
|
||||
"""直接调用MCP服务器的客户端"""
|
||||
|
||||
def __init__(self):
|
||||
print("🚀 直接MCP客户端已启动")
|
||||
print("📚 RimWorld知识库已加载")
|
||||
|
||||
def query(self, question: str) -> str:
|
||||
"""查询RimWorld知识库"""
|
||||
try:
|
||||
print(f"🔍 正在查询: {question}")
|
||||
result = get_context(question)
|
||||
return result
|
||||
except Exception as e:
|
||||
return f"查询出错: {e}"
|
||||
|
||||
def interactive_mode(self):
|
||||
"""交互模式"""
|
||||
print("\n" + "="*60)
|
||||
print("🎯 RimWorld知识库 - 交互模式")
|
||||
print("输入问题查询知识库,输入 'quit' 或 'exit' 退出")
|
||||
print("="*60)
|
||||
|
||||
while True:
|
||||
try:
|
||||
question = input("\n❓ 请输入您的问题: ").strip()
|
||||
|
||||
if question.lower() in ['quit', 'exit', '退出', 'q']:
|
||||
print("👋 再见!")
|
||||
break
|
||||
|
||||
if not question:
|
||||
print("⚠️ 请输入有效的问题")
|
||||
continue
|
||||
|
||||
print("\n🔄 正在搜索...")
|
||||
result = self.query(question)
|
||||
|
||||
print("\n📖 查询结果:")
|
||||
print("-" * 50)
|
||||
print(result)
|
||||
print("-" * 50)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n👋 用户中断,退出程序")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"\n❌ 出现错误: {e}")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='直接调用RimWorld MCP知识库')
|
||||
parser.add_argument('--query', '-q', type=str, help='直接查询问题')
|
||||
parser.add_argument('--interactive', '-i', action='store_true', help='进入交互模式')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
client = DirectMCPClient()
|
||||
|
||||
if args.query:
|
||||
# 直接查询模式
|
||||
result = client.query(args.query)
|
||||
print("\n📖 查询结果:")
|
||||
print("="*60)
|
||||
print(result)
|
||||
print("="*60)
|
||||
elif args.interactive:
|
||||
# 交互模式
|
||||
client.interactive_mode()
|
||||
else:
|
||||
# 默认显示帮助
|
||||
print("\n🔧 使用方法:")
|
||||
print("1. 直接查询: python direct_mcp_client.py -q \"ThingDef是什么\"")
|
||||
print("2. 交互模式: python direct_mcp_client.py -i")
|
||||
print("3. 查看帮助: python direct_mcp_client.py -h")
|
||||
|
||||
# 演示查询
|
||||
print("\n🎬 演示查询:")
|
||||
demo_result = client.query("ThingDef")
|
||||
print(demo_result[:500] + "..." if len(demo_result) > 500 else demo_result)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -55,7 +55,8 @@ KNOWLEDGE_BASE_PATHS = [
|
||||
# 初始化OpenAI客户端用于Qwen模型
|
||||
qwen_client = OpenAI(
|
||||
api_key=os.getenv("DASHSCOPE_API_KEY"),
|
||||
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
timeout=15.0 # 设置15秒超时,避免MCP初始化超时
|
||||
)
|
||||
|
||||
# 3. --- 向量缓存管理 ---
|
||||
@@ -192,36 +193,47 @@ def find_most_similar_files(question_embedding, file_embeddings, top_n=3, min_si
|
||||
return results
|
||||
|
||||
# 新增:重排序函数
|
||||
def rerank_files(question, file_matches, top_n=5):
|
||||
def rerank_files(question, file_matches, top_n=3): # 减少默认数量
|
||||
"""使用DashScope重排序API对文件进行重新排序"""
|
||||
try:
|
||||
# 限制输入数量以减少超时风险
|
||||
if len(file_matches) > 5: # 进一步限制最大输入数量以避免超时
|
||||
file_matches = file_matches[:5]
|
||||
|
||||
# 准备重排序输入
|
||||
documents = []
|
||||
for match in file_matches:
|
||||
# 读取文件内容
|
||||
try:
|
||||
with open(match['path'], 'r', encoding='utf-8') as f:
|
||||
content = f.read()[:2000] # 限制内容长度以提高效率
|
||||
content = f.read()[:1500] # 进一步限制内容长度以提高效率
|
||||
documents.append(content)
|
||||
except Exception as e:
|
||||
logging.error(f"读取文件 {match['path']} 失败: {e}")
|
||||
continue
|
||||
|
||||
if not documents:
|
||||
logging.warning("重排序时未能读取任何文件内容")
|
||||
return file_matches[:top_n]
|
||||
|
||||
# 调用重排序API
|
||||
# 调用重排序API,添加超时处理
|
||||
import time
|
||||
start_time = time.time()
|
||||
|
||||
response = dashscope.TextReRank.call(
|
||||
model='gte-rerank',
|
||||
query=question,
|
||||
documents=documents
|
||||
)
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
logging.info(f"重排序API调用耗时: {elapsed_time:.2f}秒")
|
||||
|
||||
if response.status_code == 200:
|
||||
# 根据重排序结果重新排序文件
|
||||
reranked_results = []
|
||||
for i, result in enumerate(response.output['results']):
|
||||
if i < len(file_matches):
|
||||
if i < len(file_matches) and i < len(documents): # 添加边界检查
|
||||
reranked_results.append({
|
||||
'path': file_matches[i]['path'],
|
||||
'similarity': result['relevance_score']
|
||||
@@ -417,6 +429,7 @@ def analyze_question_with_llm(question: str) -> dict:
|
||||
messages=messages,
|
||||
temperature=0.0, # 使用最低温度确保输出稳定
|
||||
max_tokens=300,
|
||||
timeout=12.0, # 12秒超时,避免MCP初始化超时
|
||||
stop=["\n\n"] # 防止模型生成过多内容
|
||||
)
|
||||
|
||||
@@ -494,13 +507,20 @@ def get_context(question: str) -> str:
|
||||
"""
|
||||
logging.info(f"收到问题: {question}")
|
||||
|
||||
# 使用LLM分析问题
|
||||
analysis = analyze_question_with_llm(question)
|
||||
keywords = analysis["search_keywords"]
|
||||
|
||||
if not keywords:
|
||||
logging.warning("无法从问题中提取关键词。")
|
||||
return "无法从问题中提取关键词,请提供更具体的信息。"
|
||||
try:
|
||||
# 使用LLM分析问题,添加超时保护
|
||||
analysis = analyze_question_with_llm(question)
|
||||
keywords = analysis["search_keywords"]
|
||||
|
||||
if not keywords:
|
||||
logging.warning("无法从问题中提取关键词。")
|
||||
return "无法从问题中提取关键词,请提供更具体的信息。"
|
||||
except Exception as e:
|
||||
logging.error(f"LLM分析失败,使用备用方案: {e}")
|
||||
# 备用方案:使用简单的关键词提取
|
||||
keywords = find_keywords_in_question(question)
|
||||
if not keywords:
|
||||
return "无法分析问题,请检查网络连接或稍后重试。"
|
||||
|
||||
logging.info(f"提取到关键词: {keywords}")
|
||||
|
||||
@@ -549,7 +569,7 @@ def get_context(question: str) -> str:
|
||||
|
||||
# 3. 向量化和相似度计算 (精准筛选)
|
||||
# 增加超时保护:限制向量化的文件数量
|
||||
MAX_FILES_TO_VECTORIZE = 50 # 增加处理文件数量
|
||||
MAX_FILES_TO_VECTORIZE = 10 # 进一步减少处理文件数量以避免超时
|
||||
if len(candidate_files) > MAX_FILES_TO_VECTORIZE:
|
||||
logging.warning(f"候选文件过多 ({len(candidate_files)}),仅处理前 {MAX_FILES_TO_VECTORIZE} 个。")
|
||||
candidate_files = candidate_files[:MAX_FILES_TO_VECTORIZE]
|
||||
@@ -559,27 +579,36 @@ def get_context(question: str) -> str:
|
||||
return "无法生成问题向量,请检查API连接或问题内容。"
|
||||
|
||||
file_embeddings = []
|
||||
for file_path in candidate_files:
|
||||
for i, file_path in enumerate(candidate_files):
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
# 添加处理进度日志
|
||||
if i % 5 == 0: # 每5个文件记录一次进度
|
||||
logging.info(f"正在处理第 {i+1}/{len(candidate_files)} 个文件: {os.path.basename(file_path)}")
|
||||
|
||||
file_embedding = get_embedding(content[:8000]) # 限制内容长度以提高效率
|
||||
if file_embedding:
|
||||
file_embeddings.append({'path': file_path, 'embedding': file_embedding})
|
||||
except Exception as e:
|
||||
logging.error(f"处理文件 {file_path} 时出错: {e}")
|
||||
continue # 继续处理下一个文件,而不是完全失败
|
||||
|
||||
if not file_embeddings:
|
||||
return "无法为任何候选文件生成向量。"
|
||||
logging.warning("未能为任何候选文件生成向量。可能是由于API超时或其他错误。")
|
||||
return "未能为任何候选文件生成向量,请稍后重试或减少搜索范围。"
|
||||
|
||||
# 找到最相似的多个文件
|
||||
best_matches = find_most_similar_files(question_embedding, file_embeddings, top_n=10) # 增加返回数量以供重排序
|
||||
best_matches = find_most_similar_files(question_embedding, file_embeddings, top_n=5) # 进一步减少返回数量以避免超时
|
||||
|
||||
if not best_matches:
|
||||
return "计算向量相似度失败或没有找到足够相似的文件。"
|
||||
|
||||
# 新增:重排序处理
|
||||
reranked_matches = rerank_files(question, best_matches, top_n=5)
|
||||
# 新增:重排序处理(仅在找到足够多匹配项时执行)
|
||||
if len(best_matches) > 2:
|
||||
reranked_matches = rerank_files(question, best_matches, top_n=3) # 减少重排序数量
|
||||
else:
|
||||
reranked_matches = best_matches # 如果匹配项太少,跳过重排序以节省时间
|
||||
|
||||
# 提取代码内容
|
||||
results_with_code = []
|
||||
@@ -617,5 +646,19 @@ def get_context(question: str) -> str:
|
||||
if __name__ == "__main__":
|
||||
logging.info(f"Python Executable: {sys.executable}")
|
||||
logging.info("RimWorld 向量知识库 (FastMCP版, v2.1-v4-model) 正在启动...")
|
||||
|
||||
# 快速启动:延迟初始化重量级组件
|
||||
try:
|
||||
# 验证基本配置
|
||||
if not dashscope.api_key:
|
||||
logging.warning("警告:DASHSCOPE_API_KEY 未配置,部分功能可能受限。")
|
||||
|
||||
# 创建必要目录
|
||||
os.makedirs(CACHE_DIR, exist_ok=True)
|
||||
|
||||
logging.info("MCP服务器快速启动完成,等待客户端连接...")
|
||||
except Exception as e:
|
||||
logging.error(f"服务器启动时出错: {e}")
|
||||
|
||||
# 使用 'stdio' 传输协议
|
||||
mcp.run(transport="stdio")
|
||||
105
MCP/rimworld_query.py
Normal file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
RimWorld知识库命令行工具
|
||||
快速查询工具,无需Qoder IDE
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import json
|
||||
|
||||
# 添加MCP路径
|
||||
MCP_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SDK_PATH = os.path.join(MCP_DIR, 'python-sdk', 'src')
|
||||
if SDK_PATH not in sys.path:
|
||||
sys.path.insert(0, SDK_PATH)
|
||||
|
||||
def quick_query(question: str, format_output: bool = True) -> str:
|
||||
"""快速查询函数"""
|
||||
try:
|
||||
# 动态导入避免启动时的依赖检查
|
||||
from mcpserver_stdio import get_context
|
||||
result = get_context(question)
|
||||
|
||||
if format_output:
|
||||
# 格式化输出
|
||||
lines = result.split('\n')
|
||||
formatted_lines = []
|
||||
current_section = ""
|
||||
|
||||
for line in lines:
|
||||
if line.startswith('--- 结果'):
|
||||
current_section = f"\n🔍 {line}"
|
||||
formatted_lines.append(current_section)
|
||||
elif line.startswith('文件路径:'):
|
||||
formatted_lines.append(f"📄 {line}")
|
||||
elif line.strip() and not line.startswith('---'):
|
||||
formatted_lines.append(line)
|
||||
|
||||
return '\n'.join(formatted_lines)
|
||||
else:
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
return f"❌ 查询失败: {e}"
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='RimWorld知识库命令行查询工具',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
使用示例:
|
||||
%(prog)s "ThingDef是什么"
|
||||
%(prog)s "如何创建新的Pawn" --raw
|
||||
%(prog)s "建筑物定义" --output result.txt
|
||||
%(prog)s --list-examples
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument('question', nargs='?', help='要查询的问题')
|
||||
parser.add_argument('--raw', action='store_true', help='输出原始结果,不格式化')
|
||||
parser.add_argument('--output', '-o', help='将结果保存到文件')
|
||||
parser.add_argument('--list-examples', action='store_true', help='显示查询示例')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.list_examples:
|
||||
print("📚 RimWorld知识库查询示例:")
|
||||
examples = [
|
||||
"ThingDef的定义和用法",
|
||||
"如何创建新的Building",
|
||||
"Pawn类的主要方法",
|
||||
"CompPower的使用方法",
|
||||
"XML中的defName规则",
|
||||
"GenConstruct.CanPlaceBlueprintAt",
|
||||
"Building_Door的开关逻辑"
|
||||
]
|
||||
for i, example in enumerate(examples, 1):
|
||||
print(f" {i}. {example}")
|
||||
return
|
||||
|
||||
if not args.question:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
print(f"🔍 正在查询: {args.question}")
|
||||
|
||||
result = quick_query(args.question, not args.raw)
|
||||
|
||||
if args.output:
|
||||
try:
|
||||
with open(args.output, 'w', encoding='utf-8') as f:
|
||||
f.write(result)
|
||||
print(f"✅ 结果已保存到: {args.output}")
|
||||
except Exception as e:
|
||||
print(f"❌ 保存文件失败: {e}")
|
||||
else:
|
||||
print("\n" + "="*60)
|
||||
print("📖 查询结果:")
|
||||
print("="*60)
|
||||
print(result)
|
||||
print("="*60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
123
MCP/test_config.py
Normal file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
验证MCP服务器配置和环境
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
def test_mcp_configuration():
|
||||
"""测试MCP配置是否正确"""
|
||||
print("🔍 MCP配置验证工具")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. 检查Python环境
|
||||
print(f"✓ Python解释器: {sys.executable}")
|
||||
print(f"✓ Python版本: {sys.version}")
|
||||
|
||||
# 2. 检查工作目录
|
||||
mcp_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
print(f"✓ MCP目录: {mcp_dir}")
|
||||
|
||||
# 3. 检查MCP SDK
|
||||
sdk_path = os.path.join(mcp_dir, 'python-sdk', 'src')
|
||||
print(f"✓ SDK路径: {sdk_path}")
|
||||
print(f"✓ SDK存在: {os.path.exists(sdk_path)}")
|
||||
|
||||
# 4. 检查必要文件
|
||||
server_script = os.path.join(mcp_dir, 'mcpserver_stdio.py')
|
||||
env_file = os.path.join(mcp_dir, '.env')
|
||||
print(f"✓ 服务器脚本: {os.path.exists(server_script)}")
|
||||
print(f"✓ 环境文件: {os.path.exists(env_file)}")
|
||||
|
||||
# 5. 检查依赖包
|
||||
try:
|
||||
import mcp
|
||||
print("✓ MCP SDK: 已安装")
|
||||
except ImportError as e:
|
||||
print(f"❌ MCP SDK: 未安装 - {e}")
|
||||
|
||||
try:
|
||||
import dashscope
|
||||
print("✓ DashScope: 已安装")
|
||||
except ImportError as e:
|
||||
print(f"❌ DashScope: 未安装 - {e}")
|
||||
|
||||
try:
|
||||
import openai
|
||||
print("✓ OpenAI: 已安装")
|
||||
except ImportError as e:
|
||||
print(f"❌ OpenAI: 未安装 - {e}")
|
||||
|
||||
# 6. 生成正确的配置
|
||||
python_exe = sys.executable.replace("\\", "\\\\")
|
||||
mcp_dir_escaped = mcp_dir.replace("\\", "\\\\")
|
||||
sdk_path_escaped = sdk_path.replace("\\", "\\\\")
|
||||
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"rimworld-knowledge-base": {
|
||||
"command": python_exe,
|
||||
"args": ["mcpserver_stdio.py"],
|
||||
"cwd": mcp_dir_escaped,
|
||||
"disabled": False,
|
||||
"alwaysAllow": [],
|
||||
"env": {
|
||||
"PYTHONPATH": sdk_path_escaped
|
||||
}
|
||||
}
|
||||
},
|
||||
"tools": {
|
||||
"rimworld-knowledge-base": {
|
||||
"description": "从RimWorld本地知识库(包括C#源码和XML)中检索上下文。",
|
||||
"server_name": "rimworld-knowledge-base",
|
||||
"tool_name": "get_context",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"question": {
|
||||
"type": "string",
|
||||
"description": "关于RimWorld开发的问题,应包含代码或XML中的关键词。"
|
||||
}
|
||||
},
|
||||
"required": ["question"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print("\\n📋 建议的MCP配置:")
|
||||
print("=" * 50)
|
||||
print(json.dumps(config, indent=2, ensure_ascii=False))
|
||||
|
||||
# 7. 测试服务器启动
|
||||
print("\\n🚀 测试服务器启动:")
|
||||
print("=" * 50)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[sys.executable, server_script],
|
||||
cwd=mcp_dir,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
if result.returncode == 0:
|
||||
print("✓ 服务器可以正常启动")
|
||||
else:
|
||||
print(f"❌ 服务器启动失败: {result.stderr}")
|
||||
except subprocess.TimeoutExpired:
|
||||
print("✓ 服务器启动正常(超时保护触发)")
|
||||
except Exception as e:
|
||||
print(f"❌ 服务器测试失败: {e}")
|
||||
|
||||
print("\\n🎯 配置建议:")
|
||||
print("=" * 50)
|
||||
print("1. 复制上面的配置到 Qoder IDE 的 MCP 设置中")
|
||||
print("2. 确保所有依赖包都已安装")
|
||||
print("3. 检查 .env 文件中的 API Key 配置")
|
||||
print("4. 重启 Qoder IDE 并重新连接 MCP 服务器")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_mcp_configuration()
|
||||
149
MCP/test_mcp.py
Normal file
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
最终功能测试:验证MCP服务器是否能正常工作
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
import json
|
||||
|
||||
def test_mcp_server_final():
|
||||
"""最终测试MCP服务器功能"""
|
||||
print("🔥 MCP服务器最终功能测试")
|
||||
print("=" * 50)
|
||||
|
||||
# 获取当前目录
|
||||
mcp_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
script_path = os.path.join(mcp_dir, 'mcpserver_stdio.py')
|
||||
|
||||
try:
|
||||
# 1. 验证SDK安装
|
||||
try:
|
||||
import mcp
|
||||
print("✅ MCP SDK: 已正确安装")
|
||||
except ImportError:
|
||||
print("❌ MCP SDK: 未安装")
|
||||
return False
|
||||
|
||||
# 2. 启动服务器
|
||||
print("🚀 启动MCP服务器...")
|
||||
process = subprocess.Popen(
|
||||
[sys.executable, script_path],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
cwd=mcp_dir
|
||||
)
|
||||
|
||||
# 等待启动
|
||||
time.sleep(2)
|
||||
|
||||
# 3. 初始化测试
|
||||
init_request = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "initialize",
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {
|
||||
"name": "final-test-client",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print("📡 发送初始化请求...")
|
||||
process.stdin.write(json.dumps(init_request) + '\n')
|
||||
process.stdin.flush()
|
||||
|
||||
# 读取初始化响应
|
||||
response = process.stdout.readline()
|
||||
if response:
|
||||
response_data = json.loads(response.strip())
|
||||
if "result" in response_data:
|
||||
print("✅ 初始化成功")
|
||||
print(f" 服务器名称: {response_data['result'].get('serverInfo', {}).get('name', 'unknown')}")
|
||||
print(f" 服务器版本: {response_data['result'].get('serverInfo', {}).get('version', 'unknown')}")
|
||||
else:
|
||||
print("❌ 初始化失败")
|
||||
return False
|
||||
else:
|
||||
print("❌ 初始化无响应")
|
||||
return False
|
||||
|
||||
# 4. 工具列表测试
|
||||
tools_request = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "tools/list"
|
||||
}
|
||||
|
||||
print("🔧 请求工具列表...")
|
||||
process.stdin.write(json.dumps(tools_request) + '\n')
|
||||
process.stdin.flush()
|
||||
|
||||
tools_response = process.stdout.readline()
|
||||
if tools_response:
|
||||
tools_data = json.loads(tools_response.strip())
|
||||
if "result" in tools_data and "tools" in tools_data["result"]:
|
||||
tools = tools_data["result"]["tools"]
|
||||
print(f"✅ 发现 {len(tools)} 个工具:")
|
||||
for tool in tools:
|
||||
print(f" - {tool.get('name', 'unknown')}: {tool.get('description', 'no description')}")
|
||||
else:
|
||||
print("❌ 获取工具列表失败")
|
||||
else:
|
||||
print("❌ 工具列表请求无响应")
|
||||
|
||||
print("\n🎯 测试结果:")
|
||||
print("✅ MCP服务器能够正常启动")
|
||||
print("✅ 初始化协议工作正常")
|
||||
print("✅ 工具发现机制正常")
|
||||
print("\n✨ 所有基本功能测试通过!")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 测试过程中出错: {e}")
|
||||
return False
|
||||
|
||||
finally:
|
||||
# 清理进程
|
||||
try:
|
||||
process.terminate()
|
||||
process.wait(timeout=5)
|
||||
except:
|
||||
try:
|
||||
process.kill()
|
||||
except:
|
||||
pass
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("开始最终测试...")
|
||||
success = test_mcp_server_final()
|
||||
|
||||
if success:
|
||||
print("\n🎉 恭喜!MCP服务器已完全修复并正常工作!")
|
||||
print("\n📋 现在您需要在Qoder IDE中更新配置:")
|
||||
print("1. 打开Qoder IDE设置 → MCP")
|
||||
print("2. 更新配置文件,确保使用正确的绝对路径")
|
||||
print("3. 重启Qoder IDE")
|
||||
print("4. 在Agent模式下测试知识库查询")
|
||||
print("\n建议的配置:")
|
||||
print(json.dumps({
|
||||
"mcpServers": {
|
||||
"rimworld-knowledge-base": {
|
||||
"command": sys.executable,
|
||||
"args": ["mcpserver_stdio.py"],
|
||||
"cwd": os.path.dirname(os.path.abspath(__file__)),
|
||||
"disabled": False,
|
||||
"alwaysAllow": []
|
||||
}
|
||||
}
|
||||
}, indent=2))
|
||||
else:
|
||||
print("\n❌ 仍存在问题,需要进一步调试")
|
||||
150
MCP/test_mcp_timeout_fix.py
Normal file
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
测试MCP服务器超时修复
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
import json
|
||||
|
||||
def test_mcp_server_timeout_fix():
|
||||
"""测试MCP服务器是否能快速启动并响应"""
|
||||
print("开始测试MCP服务器超时修复...")
|
||||
|
||||
# 获取当前目录
|
||||
mcp_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
script_path = os.path.join(mcp_dir, 'mcpserver_stdio.py')
|
||||
|
||||
try:
|
||||
# 启动MCP服务器进程
|
||||
print("启动MCP服务器...")
|
||||
start_time = time.time()
|
||||
|
||||
process = subprocess.Popen(
|
||||
[sys.executable, script_path],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
cwd=mcp_dir
|
||||
)
|
||||
|
||||
# 等待服务器启动(减少等待时间)
|
||||
time.sleep(2) # 从3秒减少到2秒
|
||||
|
||||
startup_time = time.time() - start_time
|
||||
print(f"服务器启动耗时: {startup_time:.2f}秒")
|
||||
|
||||
# 发送初始化请求
|
||||
init_request = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "initialize",
|
||||
"params": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {},
|
||||
"clientInfo": {
|
||||
"name": "test-client",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print("发送初始化请求...")
|
||||
request_start = time.time()
|
||||
process.stdin.write(json.dumps(init_request) + '\n')
|
||||
process.stdin.flush()
|
||||
|
||||
# 读取响应
|
||||
response_line = process.stdout.readline()
|
||||
init_time = time.time() - request_start
|
||||
|
||||
if response_line:
|
||||
print(f"✅ 初始化成功,耗时: {init_time:.2f}秒")
|
||||
print(f"收到响应: {response_line.strip()}")
|
||||
else:
|
||||
print("❌ 初始化失败:无响应")
|
||||
return False
|
||||
|
||||
# 发送简单的工具调用请求
|
||||
tool_request = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "get_context",
|
||||
"arguments": {
|
||||
"question": "ThingDef" # 简单的测试查询
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print("发送工具调用请求...")
|
||||
tool_start = time.time()
|
||||
process.stdin.write(json.dumps(tool_request) + '\n')
|
||||
process.stdin.flush()
|
||||
|
||||
# 等待响应(减少超时时间)
|
||||
timeout = 20 # 从30秒减少到20秒
|
||||
response_received = False
|
||||
|
||||
while time.time() - tool_start < timeout:
|
||||
if process.poll() is not None:
|
||||
print("服务器进程已退出")
|
||||
break
|
||||
|
||||
response_line = process.stdout.readline()
|
||||
if response_line:
|
||||
tool_time = time.time() - tool_start
|
||||
print(f"✅ 工具调用成功,耗时: {tool_time:.2f}秒")
|
||||
print(f"工具调用响应: {response_line.strip()[:200]}...") # 只显示前200个字符
|
||||
response_received = True
|
||||
break
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
total_time = time.time() - start_time
|
||||
|
||||
if response_received:
|
||||
print(f"✅ 测试成功:MCP服务器能够正常处理请求")
|
||||
print(f"总耗时: {total_time:.2f}秒")
|
||||
|
||||
# 性能评估
|
||||
if total_time < 15:
|
||||
print("🚀 性能优秀:服务器响应速度很快")
|
||||
elif total_time < 25:
|
||||
print("✅ 性能良好:服务器响应速度可接受")
|
||||
else:
|
||||
print("⚠️ 性能一般:服务器响应较慢,可能仍有超时风险")
|
||||
|
||||
else:
|
||||
print("❌ 测试失败:超时未收到响应")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 测试出错: {e}")
|
||||
return False
|
||||
|
||||
finally:
|
||||
# 清理进程
|
||||
try:
|
||||
process.terminate()
|
||||
process.wait(timeout=5)
|
||||
except:
|
||||
try:
|
||||
process.kill()
|
||||
except:
|
||||
pass
|
||||
print("测试完成")
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = test_mcp_server_timeout_fix()
|
||||
if success:
|
||||
print("\n🎉 MCP服务器超时问题已修复!")
|
||||
print("现在可以在Qoder IDE中重新连接MCP服务器了。")
|
||||
else:
|
||||
print("\n❌ MCP服务器仍存在问题,需要进一步调试。")
|
||||
249
MCP/web_api_server.py
Normal file
@@ -0,0 +1,249 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
RimWorld知识库Web API服务器
|
||||
提供HTTP接口,可以通过浏览器或HTTP客户端访问
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
import threading
|
||||
import webbrowser
|
||||
|
||||
# 添加MCP路径
|
||||
MCP_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SDK_PATH = os.path.join(MCP_DIR, 'python-sdk', 'src')
|
||||
if SDK_PATH not in sys.path:
|
||||
sys.path.insert(0, SDK_PATH)
|
||||
|
||||
class RimWorldAPIHandler(BaseHTTPRequestHandler):
|
||||
"""HTTP请求处理器"""
|
||||
|
||||
def do_GET(self):
|
||||
"""处理GET请求"""
|
||||
parsed_url = urlparse(self.path)
|
||||
|
||||
if parsed_url.path == '/':
|
||||
self.serve_web_interface()
|
||||
elif parsed_url.path == '/query':
|
||||
self.handle_query_get(parsed_url)
|
||||
elif parsed_url.path == '/api/query':
|
||||
self.handle_api_query_get(parsed_url)
|
||||
else:
|
||||
self.send_error(404, "Not Found")
|
||||
|
||||
def do_POST(self):
|
||||
"""处理POST请求"""
|
||||
if self.path == '/api/query':
|
||||
self.handle_api_query_post()
|
||||
else:
|
||||
self.send_error(404, "Not Found")
|
||||
|
||||
def serve_web_interface(self):
|
||||
"""提供Web界面"""
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>RimWorld 知识库</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||||
.header { text-align: center; margin-bottom: 30px; }
|
||||
.query-box { margin-bottom: 20px; }
|
||||
input[type="text"] { width: 70%; padding: 10px; font-size: 16px; }
|
||||
button { padding: 10px 20px; font-size: 16px; background: #007cba; color: white; border: none; cursor: pointer; }
|
||||
button:hover { background: #005a8b; }
|
||||
.result { margin-top: 20px; padding: 15px; background: #f5f5f5; border-radius: 5px; white-space: pre-wrap; }
|
||||
.loading { color: #666; font-style: italic; }
|
||||
.examples { margin-top: 20px; }
|
||||
.example { cursor: pointer; color: #007cba; margin: 5px 0; }
|
||||
.example:hover { text-decoration: underline; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>🎮 RimWorld 知识库</h1>
|
||||
<p>直接查询RimWorld游戏的C#源码和XML定义</p>
|
||||
</div>
|
||||
|
||||
<div class="query-box">
|
||||
<input type="text" id="queryInput" placeholder="输入您的问题,例如:ThingDef是什么?" onkeypress="handleKeyPress(event)">
|
||||
<button onclick="performQuery()">🔍 查询</button>
|
||||
</div>
|
||||
|
||||
<div class="examples">
|
||||
<h3>💡 查询示例:</h3>
|
||||
<div class="example" onclick="setQuery('ThingDef的定义和用法')">• ThingDef的定义和用法</div>
|
||||
<div class="example" onclick="setQuery('如何创建Building')">• 如何创建Building</div>
|
||||
<div class="example" onclick="setQuery('Pawn类的主要方法')">• Pawn类的主要方法</div>
|
||||
<div class="example" onclick="setQuery('CompPower的使用')">• CompPower的使用</div>
|
||||
</div>
|
||||
|
||||
<div id="result" class="result" style="display: none;"></div>
|
||||
|
||||
<script>
|
||||
async function performQuery() {
|
||||
const input = document.getElementById('queryInput');
|
||||
const result = document.getElementById('result');
|
||||
const query = input.value.trim();
|
||||
|
||||
if (!query) {
|
||||
alert('请输入查询问题');
|
||||
return;
|
||||
}
|
||||
|
||||
result.style.display = 'block';
|
||||
result.textContent = '🔄 正在查询,请稍候...';
|
||||
result.className = 'result loading';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/query', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({question: query})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
result.textContent = data.result;
|
||||
result.className = 'result';
|
||||
} else {
|
||||
result.textContent = '❌ 查询失败: ' + data.error;
|
||||
result.className = 'result';
|
||||
}
|
||||
} catch (error) {
|
||||
result.textContent = '❌ 网络错误: ' + error.message;
|
||||
result.className = 'result';
|
||||
}
|
||||
}
|
||||
|
||||
function setQuery(query) {
|
||||
document.getElementById('queryInput').value = query;
|
||||
}
|
||||
|
||||
function handleKeyPress(event) {
|
||||
if (event.key === 'Enter') {
|
||||
performQuery();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
||||
self.end_headers()
|
||||
self.wfile.write(html.encode('utf-8'))
|
||||
|
||||
def handle_query_get(self, parsed_url):
|
||||
"""处理GET查询请求"""
|
||||
params = parse_qs(parsed_url.query)
|
||||
question = params.get('q', [''])[0]
|
||||
|
||||
if not question:
|
||||
self.send_error(400, "Missing 'q' parameter")
|
||||
return
|
||||
|
||||
try:
|
||||
from mcpserver_stdio import get_context
|
||||
result = get_context(question)
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'text/plain; charset=utf-8')
|
||||
self.end_headers()
|
||||
self.wfile.write(result.encode('utf-8'))
|
||||
except Exception as e:
|
||||
self.send_error(500, f"Query failed: {e}")
|
||||
|
||||
def handle_api_query_get(self, parsed_url):
|
||||
"""处理API GET查询"""
|
||||
params = parse_qs(parsed_url.query)
|
||||
question = params.get('q', [''])[0]
|
||||
|
||||
if not question:
|
||||
response = {"success": False, "error": "Missing 'q' parameter"}
|
||||
else:
|
||||
try:
|
||||
from mcpserver_stdio import get_context
|
||||
result = get_context(question)
|
||||
response = {"success": True, "result": result}
|
||||
except Exception as e:
|
||||
response = {"success": False, "error": str(e)}
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(response, ensure_ascii=False).encode('utf-8'))
|
||||
|
||||
def handle_api_query_post(self):
|
||||
"""处理API POST查询"""
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
post_data = self.rfile.read(content_length)
|
||||
|
||||
try:
|
||||
data = json.loads(post_data.decode('utf-8'))
|
||||
question = data.get('question', '')
|
||||
|
||||
if not question:
|
||||
response = {"success": False, "error": "Missing 'question' field"}
|
||||
else:
|
||||
from mcpserver_stdio import get_context
|
||||
result = get_context(question)
|
||||
response = {"success": True, "result": result}
|
||||
except Exception as e:
|
||||
response = {"success": False, "error": str(e)}
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.end_headers()
|
||||
self.wfile.write(json.dumps(response, ensure_ascii=False).encode('utf-8'))
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""自定义日志输出"""
|
||||
print(f"[{self.address_string()}] {format % args}")
|
||||
|
||||
def start_server(port=8080, open_browser=True):
|
||||
"""启动Web服务器"""
|
||||
server_address = ('', port)
|
||||
httpd = HTTPServer(server_address, RimWorldAPIHandler)
|
||||
|
||||
print(f"🌐 RimWorld知识库Web服务器启动")
|
||||
print(f"📍 服务地址: http://localhost:{port}")
|
||||
print(f"🔍 查询API: http://localhost:{port}/api/query?q=您的问题")
|
||||
print(f"💻 Web界面: http://localhost:{port}")
|
||||
print("按 Ctrl+C 停止服务器")
|
||||
|
||||
if open_browser:
|
||||
# 延迟打开浏览器
|
||||
def open_browser_delayed():
|
||||
import time
|
||||
time.sleep(1)
|
||||
webbrowser.open(f'http://localhost:{port}')
|
||||
|
||||
thread = threading.Thread(target=open_browser_delayed)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\n🛑 服务器已停止")
|
||||
httpd.shutdown()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='RimWorld知识库Web API服务器')
|
||||
parser.add_argument('--port', '-p', type=int, default=8080, help='服务器端口 (默认: 8080)')
|
||||
parser.add_argument('--no-browser', action='store_true', help='不自动打开浏览器')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
start_server(args.port, not args.no_browser)
|
||||
102
MCP/使用指南.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# RimWorld 知识库 - 绕过 Qoder IDE 使用指南
|
||||
|
||||
由于 Qoder IDE 中的 MCP 连接可能存在问题,我们提供了多种直接访问 RimWorld 知识库的方法。
|
||||
|
||||
## 🚀 方法 1:直接 Python 调用
|
||||
|
||||
最简单直接的方法:
|
||||
|
||||
```bash
|
||||
# 直接查询
|
||||
python direct_mcp_client.py -q "ThingDef是什么"
|
||||
|
||||
# 交互模式
|
||||
python direct_mcp_client.py -i
|
||||
|
||||
# 查看帮助
|
||||
python direct_mcp_client.py -h
|
||||
```
|
||||
|
||||
### 优点:
|
||||
- ✅ 最快速,无需额外依赖
|
||||
- ✅ 支持交互模式
|
||||
- ✅ 直接在命令行使用
|
||||
|
||||
## 🛠️ 方法 2:命令行工具
|
||||
|
||||
专业的命令行查询工具:
|
||||
|
||||
```bash
|
||||
# 基本查询
|
||||
python rimworld_query.py "ThingDef的定义"
|
||||
|
||||
# 保存结果到文件
|
||||
python rimworld_query.py "Building类的方法" --output building_info.txt
|
||||
|
||||
# 显示原始结果(不格式化)
|
||||
python rimworld_query.py "Pawn类" --raw
|
||||
|
||||
# 查看示例
|
||||
python rimworld_query.py --list-examples
|
||||
```
|
||||
|
||||
### 优点:
|
||||
- ✅ 结果可保存到文件
|
||||
- ✅ 支持原始输出格式
|
||||
- ✅ 内置查询示例
|
||||
|
||||
## 📝 常用查询示例
|
||||
|
||||
```bash
|
||||
# 查询类定义
|
||||
"ThingDef的定义和用法"
|
||||
"Building类有哪些方法"
|
||||
"Pawn类的构造函数"
|
||||
|
||||
# 查询特定方法
|
||||
"GenConstruct.CanPlaceBlueprintAt 方法"
|
||||
"Building_Door 的开关逻辑"
|
||||
"CompPower 的电力管理"
|
||||
|
||||
# 查询XML相关
|
||||
"XML中的defName规则"
|
||||
"如何定义新的ThingDef"
|
||||
"建筑物的XML结构"
|
||||
```
|
||||
|
||||
## 🔧 故障排除
|
||||
|
||||
### 如果出现导入错误:
|
||||
```bash
|
||||
# 确保在正确的目录
|
||||
cd "C:\Steam\steamapps\common\RimWorld\Mods\3516260226\MCP"
|
||||
|
||||
# 检查 Python 环境
|
||||
python -c "import mcp; print('MCP SDK 正常')"
|
||||
```
|
||||
|
||||
### 如果查询结果为空:
|
||||
- 尝试使用更具体的关键词
|
||||
- 检查关键词拼写
|
||||
- 使用英文类名或方法名
|
||||
|
||||
### 如果 Web 服务器无法启动:
|
||||
- 检查端口是否被占用
|
||||
- 尝试使用不同的端口号
|
||||
- 确保没有其他程序占用该端口
|
||||
|
||||
## 💡 推荐使用场景
|
||||
|
||||
- **快速查询**: 使用方法 1 (direct_mcp_client.py)
|
||||
- **批量处理**: 使用方法 2 (rimworld_query.py)
|
||||
- **团队共享**: 使用方法 3 (web_api_server.py)
|
||||
- **集成开发**: 使用 Web API 接口
|
||||
|
||||
## 🎯 性能优化
|
||||
|
||||
所有方法都已经过优化:
|
||||
- 向量化处理限制在 10 个文件以内
|
||||
- API 调用超时设置为 12-15 秒
|
||||
- 支持本地缓存加速重复查询
|
||||
|
||||
现在您可以完全绕过 Qoder IDE,直接使用 RimWorld 知识库了!
|
||||
@@ -5,14 +5,7 @@
|
||||
"path": "../.."
|
||||
},
|
||||
{
|
||||
"name": "Data",
|
||||
"path": "../../../../Data"
|
||||
},
|
||||
{
|
||||
"path": "../../../../../../workshop/content/294100/3534748687"
|
||||
},
|
||||
{
|
||||
"path": "../../../../../../workshop/content/294100/3550544871"
|
||||
"path": "../../../../../../workshop/content/294100/3551234893/1.6/Assemblies/ShelterShuttle"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
|
||||
146
Source/WulaFallenEmpire/WULA_Shuttle/Building_PocketMapExit.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
/// <summary>
|
||||
/// 口袋空间退出点建筑 - 继承自MapPortal以获得完整的双向传送功能
|
||||
/// </summary>
|
||||
public class Building_PocketMapExit : MapPortal
|
||||
{
|
||||
/// <summary>目标地图</summary>
|
||||
public Map targetMap;
|
||||
|
||||
/// <summary>目标位置</summary>
|
||||
public IntVec3 targetPos;
|
||||
|
||||
/// <summary>父穿梭机</summary>
|
||||
public Building_ArmedShuttleWithPocket parentShuttle;
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
Scribe_References.Look(ref targetMap, "targetMap");
|
||||
Scribe_Values.Look(ref targetPos, "targetPos");
|
||||
Scribe_References.Look(ref parentShuttle, "parentShuttle");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写获取其他地图,返回主地图(模仿原版MapPortal.GetOtherMap)
|
||||
/// </summary>
|
||||
public override Map GetOtherMap()
|
||||
{
|
||||
// 动态更新目标地图,处理穿梭机移动的情况
|
||||
UpdateTargetFromParentShuttle();
|
||||
return targetMap;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写获取目标位置,返回主地图上的穿梭机位置(模仿原版MapPortal.GetDestinationLocation)
|
||||
/// </summary>
|
||||
public override IntVec3 GetDestinationLocation()
|
||||
{
|
||||
// 动态更新目标位置,处理穿梭机移动的情况
|
||||
UpdateTargetFromParentShuttle();
|
||||
return targetPos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从父穿梭机动态更新目标位置,处理穿梭机移动的情况
|
||||
/// </summary>
|
||||
private void UpdateTargetFromParentShuttle()
|
||||
{
|
||||
if (parentShuttle != null && parentShuttle.Spawned)
|
||||
{
|
||||
// 如果穿梭机还在地图上,更新目标位置
|
||||
if (targetMap != parentShuttle.Map || targetPos != parentShuttle.Position)
|
||||
{
|
||||
targetMap = parentShuttle.Map;
|
||||
targetPos = parentShuttle.Position;
|
||||
Log.Message($"[WULA] Updated exit target to shuttle location: {targetMap?.uniqueID} at {targetPos}");
|
||||
}
|
||||
}
|
||||
else if (parentShuttle != null && !parentShuttle.Spawned)
|
||||
{
|
||||
// 穿梭机不在地图上(可能在飞行中)
|
||||
// 保持原有目标,但记录警告
|
||||
if (this.IsHashIntervalTick(2500)) // 每隔一段时间检查一次
|
||||
{
|
||||
Log.Warning($"[WULA] Parent shuttle is not spawned, exit target may be outdated. Last known: {targetMap?.uniqueID} at {targetPos}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写是否可进入,检查目标地图是否存在(模仿原版MapPortal.IsEnterable)
|
||||
/// </summary>
|
||||
public override bool IsEnterable(out string reason)
|
||||
{
|
||||
if (targetMap == null)
|
||||
{
|
||||
reason = "WULA.PocketSpace.NoTargetMap".Translate();
|
||||
return false;
|
||||
}
|
||||
reason = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写进入事件,处理从口袋空间退出到主地图(模仿原版MapPortal.OnEntered)
|
||||
/// </summary>
|
||||
public override void OnEntered(Pawn pawn)
|
||||
{
|
||||
// 不调用 base.OnEntered,因为我们不需要原版的通知机制
|
||||
// 直接处理退出逻辑
|
||||
if (targetMap != null && pawn.Spawned)
|
||||
{
|
||||
ExitPocketSpace(pawn);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写进入按钮文本
|
||||
/// </summary>
|
||||
public override string EnterString => "WULA.PocketSpace.ExitToMainMap".Translate();
|
||||
|
||||
/// <summary>
|
||||
/// 重写进入按钮图标,使用原版的ViewCave图标
|
||||
/// </summary>
|
||||
protected override Texture2D EnterTex => ContentFinder<Texture2D>.Get("UI/Commands/ViewCave");
|
||||
|
||||
/// <summary>
|
||||
/// 单个人员退出口袋空间(简化版本,利用MapPortal功能)
|
||||
/// </summary>
|
||||
private void ExitPocketSpace(Pawn pawn)
|
||||
{
|
||||
if (targetMap == null || !pawn.Spawned) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 在目标地图找一个安全位置
|
||||
IntVec3 exitPos = CellFinder.RandomClosewalkCellNear(targetPos, targetMap, 3, p => p.Standable(targetMap));
|
||||
|
||||
// 传送人员
|
||||
pawn.DeSpawn();
|
||||
GenPlace.TryPlaceThing(pawn, exitPos, targetMap, ThingPlaceMode.Near);
|
||||
|
||||
// 切换到主地图
|
||||
if (pawn.IsColonistPlayerControlled)
|
||||
{
|
||||
Current.Game.CurrentMap = targetMap;
|
||||
Find.CameraDriver.JumpToCurrentMapLoc(exitPos);
|
||||
}
|
||||
|
||||
Messages.Message("WULA.PocketSpace.ExitSuccess".Translate(pawn.LabelShort), MessageTypeDefOf.PositiveEvent);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Log.Error($"[WULA] Error exiting pocket space: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RimWorld;
|
||||
using Verse;
|
||||
|
||||
namespace WulaFallenEmpire
|
||||
{
|
||||
/// <summary>
|
||||
/// 13x13小型口袋空间生成器
|
||||
/// 创建一个简单的13x13空间,边缘是墙,中间是空地,适合作为穿梭机内部空间
|
||||
/// </summary>
|
||||
public class GenStep_WulaPocketSpaceSmall : GenStep
|
||||
{
|
||||
public override int SeedPart => 928735; // 不同于AncientStockpile的种子
|
||||
|
||||
public override void Generate(Map map, GenStepParams parms)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Message($"[WULA] Generating WULA pocket space, map size: {map.Size}");
|
||||
|
||||
// 获取地图边界
|
||||
IntVec3 mapSize = map.Size;
|
||||
|
||||
// 生成外围岩石墙壁
|
||||
GenerateWalls(map);
|
||||
|
||||
// 生成内部地板
|
||||
GenerateFloor(map);
|
||||
|
||||
// 生成一些基础设施(照明等)
|
||||
GenerateBasicInfrastructure(map);
|
||||
|
||||
Log.Message("[WULA] WULA pocket space generation completed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[WULA] Error generating WULA pocket space: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成外围墙壁
|
||||
/// </summary>
|
||||
private void GenerateWalls(Map map)
|
||||
{
|
||||
IntVec3 mapSize = map.Size;
|
||||
|
||||
// 获取地形和物品定义
|
||||
TerrainDef roughTerrain = DefDatabase<TerrainDef>.GetNamed("Granite_Rough", false) ??
|
||||
DefDatabase<TerrainDef>.GetNamed("Granite_Smooth", false) ??
|
||||
DefDatabase<TerrainDef>.GetNamed("Sandstone_Rough", false);
|
||||
|
||||
ThingDef rockWallDef = DefDatabase<ThingDef>.GetNamed("Wall_Rock", false) ??
|
||||
DefDatabase<ThingDef>.GetNamed("Wall", false);
|
||||
|
||||
// 遍历地图边缘,放置WulaWall
|
||||
for (int x = 0; x < mapSize.x; x++)
|
||||
{
|
||||
for (int z = 0; z < mapSize.z; z++)
|
||||
{
|
||||
// 如果是边缘位置,放置WulaWall
|
||||
if (x == 0 || x == mapSize.x - 1 || z == 0 || z == mapSize.z - 1)
|
||||
{
|
||||
IntVec3 pos = new IntVec3(x, 0, z);
|
||||
|
||||
// 设置地形为岩石基础
|
||||
if (roughTerrain != null)
|
||||
{
|
||||
map.terrainGrid.SetTerrain(pos, roughTerrain);
|
||||
}
|
||||
|
||||
// 放置WulaWall
|
||||
ThingDef wallDef = DefDatabase<ThingDef>.GetNamed("WulaWall", false);
|
||||
if (wallDef != null)
|
||||
{
|
||||
Thing wall = ThingMaker.MakeThing(wallDef);
|
||||
wall.SetFaction(null);
|
||||
GenPlace.TryPlaceThing(wall, pos, map, ThingPlaceMode.Direct);
|
||||
}
|
||||
else if (rockWallDef != null)
|
||||
{
|
||||
// 如果WulaWall不存在,使用原版岩石墙作为备选
|
||||
Thing wall = ThingMaker.MakeThing(rockWallDef);
|
||||
wall.SetFaction(null);
|
||||
GenPlace.TryPlaceThing(wall, pos, map, ThingPlaceMode.Direct);
|
||||
Log.Warning("[WULA] WulaWall not found, using fallback wall");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成内部地板
|
||||
/// </summary>
|
||||
private void GenerateFloor(Map map)
|
||||
{
|
||||
IntVec3 mapSize = map.Size;
|
||||
|
||||
// 为内部区域设置WulaFloor
|
||||
TerrainDef floorDef = DefDatabase<TerrainDef>.GetNamed("WulaFloor", false);
|
||||
TerrainDef fallbackFloor = floorDef ??
|
||||
DefDatabase<TerrainDef>.GetNamed("Steel", false) ??
|
||||
DefDatabase<TerrainDef>.GetNamed("MetalTile", false) ??
|
||||
DefDatabase<TerrainDef>.GetNamed("Concrete", false);
|
||||
|
||||
if (floorDef == null)
|
||||
{
|
||||
Log.Warning("[WULA] WulaFloor not found, using fallback floor");
|
||||
}
|
||||
|
||||
// 清理内部区域并设置正确的地板
|
||||
for (int x = 1; x < mapSize.x - 1; x++)
|
||||
{
|
||||
for (int z = 1; z < mapSize.z - 1; z++)
|
||||
{
|
||||
IntVec3 pos = new IntVec3(x, 0, z);
|
||||
|
||||
// 清理该位置的所有岩石和阻挡物
|
||||
ClearCellAndSetFloor(map, pos, fallbackFloor);
|
||||
}
|
||||
}
|
||||
|
||||
Log.Message($"[WULA] Set floor for internal area ({mapSize.x-2}x{mapSize.z-2}) to {(floorDef?.defName ?? fallbackFloor?.defName)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理单元格并设置地板
|
||||
/// </summary>
|
||||
private void ClearCellAndSetFloor(Map map, IntVec3 pos, TerrainDef floorDef)
|
||||
{
|
||||
if (!pos.InBounds(map)) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 获取该位置的所有物品
|
||||
List<Thing> thingsAtPos = pos.GetThingList(map).ToList(); // 创建副本避免修改时出错
|
||||
|
||||
// 清理所有建筑物和岩石(强力清理,确保地板可以放置)
|
||||
foreach (Thing thing in thingsAtPos)
|
||||
{
|
||||
bool shouldRemove = false;
|
||||
|
||||
// 检查是否为建筑物
|
||||
if (thing.def.category == ThingCategory.Building)
|
||||
{
|
||||
// 如果是自然岩石
|
||||
if (thing.def.building?.isNaturalRock == true)
|
||||
{
|
||||
shouldRemove = true;
|
||||
}
|
||||
// 或者是岩石相关的建筑
|
||||
else if (thing.def.defName.Contains("Rock") ||
|
||||
thing.def.defName.Contains("Slate") ||
|
||||
thing.def.defName.Contains("Granite") ||
|
||||
thing.def.defName.Contains("Sandstone") ||
|
||||
thing.def.defName.Contains("Limestone") ||
|
||||
thing.def.defName.Contains("Marble") ||
|
||||
thing.def.defName.Contains("Quartzite") ||
|
||||
thing.def.defName.Contains("Jade"))
|
||||
{
|
||||
shouldRemove = true;
|
||||
}
|
||||
// 或者是其他阻挡的建筑物(除了我们的乌拉墙)
|
||||
else if (!thing.def.defName.Contains("Wula") && thing.def.Fillage == FillCategory.Full)
|
||||
{
|
||||
shouldRemove = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldRemove)
|
||||
{
|
||||
if (Prefs.DevMode) // 只在开发模式下输出详细日志
|
||||
{
|
||||
Log.Message($"[WULA] Removing {thing.def.defName} at {pos} to make space for floor");
|
||||
}
|
||||
thing.Destroy(DestroyMode.Vanish);
|
||||
}
|
||||
}
|
||||
|
||||
// 在清理后稍微延迟,再检查一次(确保彻底清理)
|
||||
thingsAtPos = pos.GetThingList(map).ToList();
|
||||
foreach (Thing thing in thingsAtPos)
|
||||
{
|
||||
if (thing.def.category == ThingCategory.Building && thing.def.Fillage == FillCategory.Full)
|
||||
{
|
||||
Log.Warning($"[WULA] Force removing remaining building {thing.def.defName} at {pos}");
|
||||
thing.Destroy(DestroyMode.Vanish);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置地板地形
|
||||
if (floorDef != null)
|
||||
{
|
||||
map.terrainGrid.SetTerrain(pos, floorDef);
|
||||
if (Prefs.DevMode)
|
||||
{
|
||||
Log.Message($"[WULA] Set terrain at {pos} to {floorDef.defName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"[WULA] Error clearing cell at {pos}: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成基础设施
|
||||
/// </summary>
|
||||
private void GenerateBasicInfrastructure(Map map)
|
||||
{
|
||||
IntVec3 mapSize = map.Size;
|
||||
IntVec3 center = map.Center;
|
||||
|
||||
// 获取灯具定义
|
||||
ThingDef lampDef = DefDatabase<ThingDef>.GetNamed("StandingLamp", false) ??
|
||||
DefDatabase<ThingDef>.GetNamed("TorchLamp", false) ??
|
||||
DefDatabase<ThingDef>.GetNamed("Campfire", false);
|
||||
|
||||
if (lampDef == null)
|
||||
{
|
||||
Log.Warning("[WULA] No lamp definition found, skipping lighting generation");
|
||||
return;
|
||||
}
|
||||
|
||||
// 在四个角落放置照明设备
|
||||
var lightPositions = new List<IntVec3>
|
||||
{
|
||||
new IntVec3(2, 0, 2), // 左下角
|
||||
new IntVec3(mapSize.x - 3, 0, 2), // 右下角
|
||||
new IntVec3(2, 0, mapSize.z - 3), // 左上角
|
||||
new IntVec3(mapSize.x - 3, 0, mapSize.z - 3) // 右上角
|
||||
};
|
||||
|
||||
foreach (IntVec3 pos in lightPositions)
|
||||
{
|
||||
if (pos.InBounds(map) && pos.Standable(map))
|
||||
{
|
||||
// 放置立式灯
|
||||
Thing lamp = ThingMaker.MakeThing(lampDef);
|
||||
lamp.SetFaction(null);
|
||||
GenPlace.TryPlaceThing(lamp, pos, map, ThingPlaceMode.Direct);
|
||||
}
|
||||
}
|
||||
|
||||
// 在中心区域留出空间,这里将放置退出点
|
||||
// 不在这里放置退出点,因为这会由Building_ArmedShuttleWithPocket来处理
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,6 +169,9 @@
|
||||
<Compile Include="Projectiles\BulletWithTrail.cs" />
|
||||
<Compile Include="WULA_Shuttle\ArmedShuttleIncoming.cs" />
|
||||
<Compile Include="WULA_Shuttle\Building_ArmedShuttle.cs" />
|
||||
<Compile Include="WULA_Shuttle\Building_ArmedShuttleWithPocket.cs" />
|
||||
<Compile Include="WULA_Shuttle\Building_PocketMapExit.cs" />
|
||||
<Compile Include="WULA_Shuttle\GenStep_WulaPocketSpaceSmall.cs" />
|
||||
<Compile Include="HarmonyPatches\Patch_DropCellFinder_SkyfallerCanLandAt.cs" />
|
||||
<Compile Include="MechWeapon\FloatMenuProvider_Mech.cs" />
|
||||
<Compile Include="MechWeapon\Patch_MissingWeapon.cs" />
|
||||
|
||||
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB |