3DMGAME 3DM首页 新闻中心 前瞻 | 评测 游戏库 热门 | 最新 攻略中心 攻略 | 秘籍 下载中心 游戏 | 汉化 购买正版 侠客前传 | 刺客信条 论坛

注册 登录

QQ登录

只需一步,快速开始

查看: 32407|回复: 477

[原创] [授人以鱼不如授人以渔]MOD制作教程 : 如何从零开始制作一个Unity Mod Manager MOD

  [复制链接]

13

主题

335

帖子

4991

积分

3DM Mod原创组

Rank: 15Rank: 15Rank: 15Rank: 15Rank: 15

贡献度
771
金元
18565
积分
4991
精华
5
注册时间
2011-12-15

3DM MOD站(白银)

发表于 2019-4-3 18:12 | 显示全部楼层 |阅读模式
本帖最后由 JKstring 于 2019-4-12 19:41 编辑

开场白:其实 UnityModManager是一个非常好用的Unity游戏MOD工具,不仅仅是修仙模拟器,任何Unity3D引擎制作的游戏几乎都支持,然而国内的教程很少(也可能是我找的不仔细),相关的资料很少,于是出一个UMM MOD的制作教程,让更多的人有能力做属于自己的MOD,以下的教程基于我新写的修仙模拟器炼宝管理MOD当示例,在结尾会放出源码(学会后其他游戏的MOD也是相同制作方法)
如果对源码不感兴趣,想要MOD的朋友,可以去 点此前往 这里下载

更多源码可以去点此前往这里下载,都是以相同的方式开发,但遇到与以下示例不同的状况,或者其他的处理的方式的写法,有兴趣的朋友可以去看看




使用到的工具:
Visual Studio 2017


1.新建一个项目




首先打开VisualStudio2017(以下简称VS),单击左上角新建,项目,C#,类库(.Net Framework)如上图,为你的MOD起一个名字吧,图例中使用了MakeFaBaoManager,把其改成自己MOD的名字即可(尽量开发过程中避免使用中文字符,命名最好也不要用中文,虽然C#支持中文编译,但是出了BUG找不到原因,最后发现是用了中文的感觉太痛苦了,第一次制作最好与例子中一模一样,第二次在考虑改名)

框架要改为3.5或者4.0,推荐3.5,虽然更新的框架会有更多的功能,但实际上并用不到,而高版本的框架对使用MOD人来讲是一个门槛,他也必须要有对应的框架版本才行,所以推荐最低的3.5



2.导入游戏所用的类库




右键单击"引用",选择添加引用,在浏览中寻找以上dll添加引用

其中0Harmony12和UnityModManager可以在你UnityModManager安装目录下找到

Assembly-CSharp
Assembly-CSharp-firstpass
UnityEngine
UnityEngine.CoreModule
UnityEngine.IMGUIModule
UnityEngine.TextRenderingModule
UnityEngine.UI
UnityEngine.UIModule
以上类库可以再游戏根目录下的Amazing Cultivation Simulator_Data/Managed 文件夹下找到,其他Unity3D游戏也是相同道理Managed文件夹下




添加引用完成后,选中所有引用,在下方会有一个复制到本地,更改值为False,不让他进行复制,然后右键解决方案下面,你为MOD起的名字的菜单,右键,选择添加,新建项,文本文件,名称改为info.json,这个文件将是你MOD的信息文件,UMM依靠这个文件显示你MOD的名字,版本号等



选中新创建的info.json,将他的复制到输出目录改为始终复制,方便发布打包

然后我们右键右边Class1,选择重命名,更改名称为MainMod,会弹出提示是否更新引用路径,我们选择是,这样就与上方截图中的文件名保持一致了




我们双击MainMod.cs文件,切换到代码窗口,将我们添加的引用文件类进行使用,如上图,如此我们的准备工作就结束了,可以具体的来写我们的MOD了!


2.处理使用MOD玩家的配置文件,让其可以保存配置




UnityModManager提供了一个非常简便的方法,用来保存每个使用MOD玩家的配置文件,要使用他我们首先要创建一个类,并起名为Settings(名称可以更改,但是对于初次接触的人来说,最好按照图中示例做一遍再尝试自己修改),他继承了UnityModManager.ModSettings,然后我们重写一下save方法(override),最后完成结果如同上图一样就完成了,我们现在可以方便的存储玩家配置了

接下来我们焦点回到public class MainMod 中,我们需要创建一个入口,来让UMM加载我们的MOD,并且我们还需要几个变量开关,来让MOD是运行还是停止




此处我们新添加了三个变量,enable来确定MOD是开启的还是关闭的,settings,玩家的设置文件,玩家调的MOD选项都保存在里面,logger,用来在游戏中输出信息的,我们还添加了一个Load方法,这里是UMM直接调用我们MOD的入口,当我们的MOD被UMM找到被加载时,就会执行这里,如果我们return true,意思就是告诉umm,这个MOD已经成功加载并且完成初始化了,可以正常使用了~(如果return false,表示我启动失败了,不要加载这个MOD了,UMM此时会把这个MOD的状态改为红色,适用于我们发现MOD已经不兼容这个游戏版本了,或者这个MOD必须要在某个MOD之后,但是我们发现没有加载那个MOD,就可以return false通知使用者)


3.MOD初始化

之前说了UMM会调用我们的Load来加载MOD,我们就可以在加载MOD的时候进行初始化,比如我们之前的玩家配置文件还没读取呢,于是我们就可以这么写




每一行代码的含义都已经写在图上了,但是那里有几行红色报错了,怎么回事呢,是因为我们还没有定义这几个方法,我们将鼠标放到红线上面,就会弹出提示



我们选择显示可能的修补程序,选择生成方法



我们依次对三个带有红色的地方进行相同操作,就能得到如下图的代码



这样我们就可以让用户在开关MOD,保存配置,打开配置面板的时候,执行我们对应的代码了!







只要改为如上的代码(绿色为注释,可以不用打),基本功能就都能看到了!注意OnToggle方法,他要返回一个值,return true;就是告诉UMM我切换状态成功了,如果return false,则表示我切换失败了,可能需要用户重启游戏才能让MOD生效,UMM在接收到return false 的时候,会将MOD的状态更改为红色(UMM红色状态表示需要重启游戏或者无法使用)

4.实际修改游戏内容


游客,如果您要查看本帖隐藏内容请回复


结束语:UMM相比于官方提供的接口,给我们提供了更强大的功能,最高的权限,让我们能访问到游戏里几乎所有的数据,调用任何的方法,搭配VS的代码提示+自动完成,可以快速,简单的制作自己的MOD,并且只要学会,就可以不局限于一款游戏,为更多的游戏制作MOD,希望能以这一篇教程为引,带出更多的优质MOD!下面放出上面示例的源码,方便学习



======================================拓展知识==========================

关于Traverse的使用:
Traverse是harmony类库下的一个工具类,也就是在一开始引用的using harmony;这条语句后,方便我们使用的一个类,首先我们要明白private和public还有protected三个关键词的区别,具体可以百度,我这里仅从结论讲明,除了public,其他的private和protected从外界是无法访问到的,但是用Traverse类不管它是public,private,protected,均可以强行访问,为什么不任何地方都使用Traverse去访问呢,因为性能问题,用Traverse要走映射,简单来说运行速度会有些许影响

Traverse的具体使用方法简单的来说明一下,Traverse.create(类的实例),表名我要将一个类的实例转为Traverse对象,简单来说就是附加功能,比如我们以前都是自己买菜,后来有了XX外卖,我们不需要亲力亲为了,XX外卖就等于Traverse对象了(这里就是将映射功能简单化了,不需要自己打代码了),这样我们就有一个可以访问类实例的Traverse对象了,在上面法宝的例子中,我是直接写为了
var itemID = Traverse.Create(__instance).Field("itemID").GetValue<int>();
这是一种简化的写法的,下面我分开并且逐步注释一下
Traverse t = Traverse.Create(__instance);//根据__instance (ToilRefining类的实例) 创建 Traverse对象,并且用t表示
Traverse f = t.Field("itemID");//在ToilRefining实例里面有个itemID的字段,找到他并且创建一个Traverse对象,用f表示,这样可以强行访问 itemID,因为itemID是私有的没法直接访问
int itemID = f.GetValue<int>();//将Traverse版本的itemID提取成可以直接访问的数值,因为Traverse并不知道原本itemID是什么类型的,所以我们要用<int>标注这是个int类型了,从源代码中我们可以知道itemID的变量类型,对应修改即可
于是我们就访问到itemID了
既然有获取,自然就有修改,修改我们可以用f.SetValue(数值),这里就不需要指定<int>了,因为你在输入数值的时候,他会自动把你输入的数据转成对应的类型

这里我说一下字段,属性,方法的意思,这是C#的基础,字段代表类变量,可以理解为类中的全局变量,可以再类中任意地方访问到

属性是字段的升级版,他在源代码中的样子是这样的



他跟字段的定义差不多,但是后面会有括号,里面还有set和get,这种样子的就是属性,我们不能通过Traverse.Field(字段名字),而是通过Traverse.Property(属性名字)来访问,如果定义中只有get,表名这个东西只能获取,不能更改(就是游戏开发者也不能),get和set都在就是可以获取也可以更改

最后就是方法,在C#中称为方法,C语言中称为函数,比如游戏源码中,MakeFaBao就是制作法宝的方法,他定义时后面跟随的是()这种括号,我们想要访问游戏private的方法可以用Traverse.Method(方法名字).GetValue()来运行,注意后面要加上.GetValue(),因为仅仅Traverse.Method(方法名字)是获取的方法的Traverse对象,而没有运行他

C#中有一个关键词是var,这个关键词是这个变量是智能根据你后面赋值来判断他的变量类型的
比如 var a = 6;//a是int类型
var b = "我是文字";//b就是string类型的
于是之前为了itemID那么多行的代码就可以省略为var itemID = Traverse.Create(__instance).Field("itemID").GetValue<int>();一句话搞定
当然也可以var t = Traverse.Create(__instance);
var itemID = t.Field("itemID").GetValue<int>();var XXXX = t.Field("XXXX").GetValue<int>();
来多次获取




游客,如果您要查看本帖隐藏内容请回复





本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

评分

2

查看全部评分

2

主题

74

帖子

438

积分

高级玩家

Rank: 4

贡献度
60
金元
1882
积分
438
精华
1
注册时间
2011-12-30
发表于 2019-4-3 18:58 | 显示全部楼层
谢谢分享
回复 支持 反对

使用道具 举报

9

主题

644

帖子

2万

积分

3DM Mod原创组

Rank: 15Rank: 15Rank: 15Rank: 15Rank: 15

贡献度
262
金元
251489
积分
26197
精华
0
注册时间
2018-6-11

3DM MOD站(白银)

发表于 2019-4-3 18:59 | 显示全部楼层

回复 支持 反对

使用道具 举报

5

主题

134

帖子

292

积分

高级玩家

Rank: 4

贡献度
32
金元
1537
积分
292
精华
1
注册时间
2018-5-17
发表于 2019-4-3 19:00 | 显示全部楼层
前来学习
回复 支持 反对

使用道具 举报

0

主题

111

帖子

171

积分

中级玩家

Rank: 3Rank: 3

贡献度
0
金元
1708
积分
171
精华
0
注册时间
2017-8-1
发表于 2019-4-3 19:05 | 显示全部楼层
感谢大佬分享教程
回复 支持 反对

使用道具 举报

52

主题

908

帖子

2837

积分

版主

Rank: 14Rank: 14Rank: 14Rank: 14

贡献度
240
金元
18765
积分
2837
精华
0
注册时间
2012-10-2

疯狂页游玩家1级

发表于 2019-4-3 19:28 | 显示全部楼层

谢谢!感谢您的分享
回复 支持 反对

使用道具 举报

14

主题

1574

帖子

1786

积分

游戏狂人

Rank: 6Rank: 6

贡献度
1
金元
17816
积分
1786
精华
0
注册时间
2016-10-11
发表于 2019-4-3 20:06 | 显示全部楼层
必须顶。。。。。。。。。。。。。。。。。
回复 支持 反对

使用道具 举报

1

主题

252

帖子

373

积分

高级玩家

Rank: 4

贡献度
12
金元
3253
积分
373
精华
0
注册时间
2016-2-14
发表于 2019-4-3 23:30 | 显示全部楼层
顶,66666666666666666
回复 支持 反对

使用道具 举报

0

主题

80

帖子

120

积分

中级玩家

Rank: 3Rank: 3

贡献度
0
金元
1200
积分
120
精华
0
注册时间
2012-9-24
发表于 2019-4-4 11:25 | 显示全部楼层
kkkkk看看k
回复 支持 反对

使用道具 举报

8

主题

158

帖子

377

积分

高级玩家

Rank: 4

贡献度
47
金元
1893
积分
377
精华
0
注册时间
2019-1-27
发表于 2019-4-4 12:42 | 显示全部楼层
3333333333333333333333333333333333333
回复 支持 反对

使用道具 举报

0

主题

94

帖子

238

积分

中级玩家

Rank: 3Rank: 3

贡献度
0
金元
2384
积分
238
精华
0
注册时间
2011-4-8
发表于 2019-4-4 16:11 | 显示全部楼层
show times
回复 支持 反对

使用道具 举报

1

主题

612

帖子

1112

积分

游戏狂人

Rank: 6Rank: 6

贡献度
23
金元
10198
积分
1112
精华
0
注册时间
2013-6-12
发表于 2019-4-4 16:19 | 显示全部楼层
感谢分享
回复 支持 反对

使用道具 举报

1

主题

137

帖子

135

积分

中级玩家

Rank: 3Rank: 3

贡献度
0
金元
1347
积分
135
精华
0
注册时间
2018-8-27
发表于 2019-4-4 21:30 | 显示全部楼层
66666666666
回复 支持 反对

使用道具 举报

0

主题

258

帖子

274

积分

高级玩家

Rank: 4

贡献度
2
金元
2655
积分
274
精华
0
注册时间
2007-3-10
发表于 2019-4-4 21:40 | 显示全部楼层
感谢分享!
回复 支持 反对

使用道具 举报

0

主题

133

帖子

131

积分

中级玩家

Rank: 3Rank: 3

贡献度
0
金元
1308
积分
131
精华
0
注册时间
2019-4-1
发表于 2019-4-4 22:29 | 显示全部楼层
666666666666666
回复 支持 反对

使用道具 举报

0

主题

510

帖子

542

积分

高级玩家

Rank: 4

贡献度
0
金元
5422
积分
542
精华
0
注册时间
2010-1-24
发表于 2019-4-5 09:06 | 显示全部楼层
学习学习具体mod怎么写
回复 支持 反对

使用道具 举报

0

主题

34

帖子

34

积分

初级玩家

Rank: 2

贡献度
0
金元
340
积分
34
精华
0
注册时间
2018-9-28
发表于 2019-4-5 10:44 | 显示全部楼层
膜拜大佬讲解
回复 支持 反对

使用道具 举报

1

主题

605

帖子

492

积分

高级玩家

Rank: 4

贡献度
0
金元
4920
积分
492
精华
0
注册时间
2012-2-15
发表于 2019-4-5 15:15 | 显示全部楼层
谢谢楼主分享
回复 支持 反对

使用道具 举报

0

主题

84

帖子

144

积分

中级玩家

Rank: 3Rank: 3

贡献度
0
金元
1441
积分
144
精华
0
注册时间
2015-4-30
发表于 2019-4-5 23:15 | 显示全部楼层
9999999999999999999999
回复 支持 反对

使用道具 举报

1

主题

632

帖子

731

积分

超级玩家

Rank: 5Rank: 5

贡献度
11
金元
6868
积分
731
精华
0
注册时间
2013-9-21
发表于 2019-4-6 13:34 | 显示全部楼层
6666666666666666666666
回复 支持 反对

使用道具 举报

8

主题

158

帖子

377

积分

高级玩家

Rank: 4

贡献度
47
金元
1893
积分
377
精华
0
注册时间
2019-1-27
发表于 2019-4-6 15:33 | 显示全部楼层
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Reflection;
using UnityModManagerNet;
using UnityEngine;
using UnityEngine.UI;
using Harmony12;
using XiaWorld;
using XiaWorld.UI.InGame;
using FairyGUI;
using System.Reflection.Emit;

namespace MakeFuManager
{
    public class Settings : UnityModManager.ModSettings
    {
        public override void Save(UnityModManager.ModEntry modEntry)
        {
            Save(this, modEntry);
        }
    }
    public class MainMod
    {
        public static bool enable;
        public static Settings settings;
        public static UnityModManager.ModEntry.ModLogger logger;

        public static bool load(UnityModManager.ModEntry modEntry)
        {
            //读取用户配置文件
            settings = Settings.Load<Settings>(modEntry);
            //存储日志输出对象方便以后使用
            logger = modEntry.Logger;

            //当用户打开MOD配置时调用
            modEntry.OnGUI = OnGUI;
            //当用户保存MOD配置时调用
            modEntry.OnSaveGUI = OnSaveGUI;
            //当用户开关MOD配置时调用
            modEntry.OnToggle = OnToggle;

            //将更改应用到游戏上
            var harmony = HarmonyInstance.Create(modEntry.Info.Id);
            harmony.PatchAll(Assembly.GetExecutingAssembly());
            return true;
        }

        /// <summary>
        /// 当用户开关MOD的时候调用
        /// </summary>
        /// <param name="arg1">MOD配置对象</param>
        /// <param name="arg2">用户是要打开是关闭MOD</param>
        /// <returns>切换状态是否成功</returns>
        private static bool OnToggle(UnityModManager.ModEntry arg1, bool arg2)
        {
            //当前开启状态变为用户想要的状态
            enable = arg2;
            //告诉UMM状态切换好了
            return true;
        }

        private static void OnSaveGUI(UnityModManager.ModEntry obj)
        {
            settings.Save(obj);
        }

        private static void OnGUI(UnityModManager.ModEntry obj)
        {

        }
    }


    [HarmonyPatch(typeof(PracticeMgr), "RandomSpellItem")]
    public class PracticeMgr_RandomSpellItem_Path
    {
        [HarmonyPrefix]
        public static bool RandomSpellItem_Path(PracticeMgr __instance)
        {
            //如果MOD状态是关闭的话
            if (!MainMod.enable)
            {
                //执行游戏原本的方法,不要对其进行修改
                return true;
            }
            //不要再执行原本游戏方法了,本MOD接手了
            return false;
        }
        public ItemThing RandomSpellItem(string otname, string name, float p, float fixp = -1f, int level = -1, bool notemp = false, int rate = 0)
        {
            var ItemSpellNames = Traverse.Create(__instance).Field("ItemSpellNames").GetValue<int>();
            string itemname = string.Empty;
            if (string.IsNullOrEmpty(otname))
            {
                itemname = __instance.ItemSpellNames[World.RandomRange(0, __instance.ItemSpellNames.Count)];
            }
            else if (otname == "Item_SpellPaperLv2")
            {
                itemname = __instance.ItemSpellNames[1];
            }
            else if (otname == "Item_SpellPaperLv3")
            {
                itemname = __instance.ItemSpellNames[2];
            }
            else
            {
                itemname = __instance.ItemSpellNames[0];
            }
            if (level >= 0)
            {
                itemname = __instance.ItemSpellNames[level];
            }
            ItemThing itemThing = ItemRandomMachine.RandomItem(itemname, null, 0, 12, -1f, 1);
            itemThing.Rate = ((rate <= 0) ? itemThing.Rate : rate);
            if (string.IsNullOrEmpty(name))
            {
                itemThing.SetQuality((fixp <= 0f) ? World.RandomRange(0.1f, 0.7f) : fixp);
                fixp = -1f;
            }
            else
            {
                itemThing.SetQuality(p);
            }
            if (fixp == -1f)
            {
                fixp = 0.5f + (float)((rate <= 0) ? itemThing.Rate : rate) * 0.25f;
            }
            if (!string.IsNullOrEmpty(name))
            {
                SpellDef spellDef = __instance.GetSpellDef(name);
                if (notemp)
                {
                    fixp *= 0.6f;
                }
                foreach (ModifierPropertyData modifierPropertyData in spellDef.Effects)
                {
                    itemThing.AddEquiptData(modifierPropertyData.Name, modifierPropertyData.AddV * p * fixp, modifierPropertyData.AddP * p * fixp);
                }
                itemThing.SetName(((!notemp) ? string.Empty : global::TFMgr.Get("伪造的")) + spellDef.DisplayName);
                itemThing.SetDesc(spellDef.Desc + ((!notemp) ? string.Empty : global::TFMgr.Get("\n但似乎是伪造的,效果大打折扣。")));
            }
            else
            {
                System.Random random = new System.Random((int)p);
                int num = random.Next(1, 3);
                for (int i = 0; i < num; i++)
                {
                    string name2 = PropertyMgr.Instance.RandomProperty((int)p);
                    float quality = itemThing.GetQuality();
                    float num2 = 1f;
                    if (quality <= 0.5f)
                    {
                        num2 = Mathf.Lerp(0f, 0.2f, quality / 0.5f);
                    }
                    else if (quality <= 0.7f)
                    {
                        num2 = Mathf.Lerp(0.2f, 0.4f, (quality - 0.5f) / 0.2f);
                    }
                    else if (quality <= 1f)
                    {
                        num2 = Mathf.Lerp(0.4f, 1f, (quality - 0.7f) / 0.3f);
                    }
                    itemThing.AddEquiptData(name2, 0f, num2 * World.RandomRange(0.7f, 1.3f) * fixp);
                }
                itemThing.SetDesc(global::TFMgr.Get("一张随意为之但充满想象力的符咒,不知道会有什么神奇的效果。"));
            }
            return itemThing;
        }
    }
}

按照你的方法把 this.ItemSpellNames替换成 __instance.ItemSpellNames  可  __instance下面的红线始终存在红色波浪线。


回复 支持 反对

使用道具 举报

7

主题

1324

帖子

1210

积分

游戏狂人

Rank: 6Rank: 6

贡献度
7
金元
11817
积分
1210
精华
0
注册时间
2010-1-31
发表于 2019-4-6 16:26 | 显示全部楼层
ggwshx,如果您要查看本帖隐藏内容请回复
回复 支持 反对

使用道具 举报

7

主题

900

帖子

772

积分

超级玩家

Rank: 5Rank: 5

贡献度
17
金元
7038
积分
772
精华
0
注册时间
2008-4-19
发表于 2019-4-6 19:25 | 显示全部楼层
看来还是留给大佬吧,看不懂。
回复 支持 反对

使用道具 举报

1

主题

275

帖子

368

积分

高级玩家

Rank: 4

贡献度
1
金元
3642
积分
368
精华
0
注册时间
2011-10-3

G胖の微笑

发表于 2019-4-6 21:19 | 显示全部楼层
感谢分享!
回复 支持 反对

使用道具 举报

0

主题

399

帖子

390

积分

高级玩家

Rank: 4

贡献度
0
金元
3901
积分
390
精华
0
注册时间
2018-6-8
发表于 2019-4-7 02:22 | 显示全部楼层
6666666666666666666666
回复 支持 反对

使用道具 举报

0

主题

41

帖子

41

积分

初级玩家

Rank: 2

贡献度
0
金元
412
积分
41
精华
0
注册时间
2019-3-27
发表于 2019-4-7 09:12 | 显示全部楼层
66666666666666
回复 支持 反对

使用道具 举报

0

主题

123

帖子

151

积分

中级玩家

Rank: 3Rank: 3

贡献度
0
金元
1510
积分
151
精华
0
注册时间
2019-4-6
发表于 2019-4-7 10:20 | 显示全部楼层
感谢分享,支持大佬
回复 支持 反对

使用道具 举报

0

主题

331

帖子

394

积分

高级玩家

Rank: 4

贡献度
0
金元
3936
积分
394
精华
0
注册时间
2013-9-7
发表于 2019-4-8 02:39 | 显示全部楼层
谢谢大大
回复 支持 反对

使用道具 举报

2

主题

724

帖子

620

积分

超级玩家

Rank: 5Rank: 5

贡献度
3
金元
6076
积分
620
精华
0
注册时间
2008-8-3
发表于 2019-4-8 10:43 | 显示全部楼层
感谢分享
回复 支持 反对

使用道具 举报

0

主题

58

帖子

117

积分

中级玩家

Rank: 3Rank: 3

贡献度
0
金元
1170
积分
117
精华
0
注册时间
2012-11-16
发表于 2019-4-10 13:16 | 显示全部楼层
学习了  对新手帮助很大
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

Archiver|手机版|咖绿茵|求知网|3DMGAME ( 京ICP备14006952号-1  沪公网安备 31011202006753号

GMT+8, 2021-12-5 12:37 , Processed in 0.157945 second(s), 18 queries , Memcache On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表