战魂小筑

讨论群:309800774 知乎关注:http://zhihu.com/people/sunicdavy 开源项目:https://github.com/davyxu

   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  257 随笔 :: 0 文章 :: 506 评论 :: 0 Trackbacks

#

Patch

patch只是显示当前版本发生了怎样的变化,基于文本。例如:

文本变化:

del /s /q game\bin\*.lib
del /s /q game\bin\*.dll
+
\ No newline at end of file

 

二进制变化:

diff -r 2f3c677ed7ab -r c0efe12f9de3 game/bin/091208A002.jpg
Binary file game/bin/091208A002.jpg has changed

对于hg来说,据我所了解,无法像svn那样apply一个patch。hg的patch可以在提交后从log中提取

而svn的patch只能在未提交前提取,svn在这点上显然有些麻烦。

 

Bundle

bundle包含了版本修改的所有信息,包括二进制。其本身基于二进制,无法阅读,可以通过Repository Explorer的Add Bundle来将修改打入你的版本库。但须注意Bundle之间必须连续,如果当中跳过任何一个Bundle,都是无法成功完整的打入你的版本库的

posted @ 2010-01-29 15:56 战魂小筑 阅读(2441) | 评论 (0)编辑 收藏

      鉴于使用HgSubversion无法从SVN服务器获取版本,我找到了另外一个方法能解决公司使用SVN与自己携带方式编码的平衡点:同时使用SVN与HG进行代码管理。

      在现有的SVN文件夹下,创建HG的代码库,编辑.hgignore文件,一般对于VC项目,可以这样参考这样的写法

*中文为注释,不要写入文件

glob:.hgignore             忽略.hgignore本身
glob:.svn                    忽略所有的svn管理文件
glob:*.user                 本地用户配置文件
glob:*.suo                  solution本地配置文件
glob:*.ncb                  Intelisence库
glob:*.exe                  exe可以由代码生成
glob:*.dll                    dll可以由代码生成
glob:game/Lib             直接忽略lib里所有文件
glob:game/Obj            直接忽略obj里所有文件

这里的glob就是通配符,也可以使用工具做正则表达式

.hgignore可以由乌龟shell菜单中的Edit ignore filter来管理,而且还可以时时查看修改情况,可惜的是右边的结果居然没有排序功能……所以我的一般做法是直接在根目录Commit,在列表中查看filter结果是否正确

 

确认无误后,直接将代码提交到hg代码库,以后代码做修改时,SVN与HG会同时侦测到文件修改。一般可以自己认为HG是大的版本,而SVN是小的版本即可。 当需要带走版本时,只需要直接拷贝hg目录,或者用clone指令即可。别人需要获得一份干净的稳定版本,也可以新建一个HG版本库,从这个混合库中pull过去

 

这种做法在git+svn也是一样的,:)

posted @ 2010-01-29 11:55 战魂小筑 阅读(5351) | 评论 (4)编辑 收藏

     当我们的代码写的差不多,需要提交给其他人怎么办?这里可以将自己的电脑作为WebServer:

     在要共享的hg版本库目录中命令行中键入

     hg serve

     或者在乌龟中选择WebServer一项,就可以将本机作为WebServer,在浏览器中敲入http://127.0.0.1:8000就可以访问自己的代码库

     另外一个代码库可以通过pull指令,或者在乌龟的Repository Explorer中键入这个地址,然后使用pull菜单命令直接获取更新,然后再update,将库中最新的更新变为代码

    这里有详细教程

 

    bundle,查看文档和菜单时,经常看到这个词。它是changeset的一个集合文件,可以使用Incoming来导入

   

    patch, 这种文件一般是文档,可以查看修改,貌似可以和其他代码管理工具通用

 

    Hg权限管理貌似只有Web Server 配置里的Alllw Push, Deny Push两种方法,但是你的库只要打开,对于别人来说就等于完全访问,这一块也貌似没有人研究过,确实没有权限管理?

posted @ 2010-01-29 10:44 战魂小筑 阅读(2310) | 评论 (2)编辑 收藏

    今天试用了下Mercurial(简称Hg)分布式代码管理系统,查了下,居然乌龟这家公司做的WindowsShell,赶快下载

    Hg比Svn最大的优势就是在于

    1. 分布式代码管理,无需服务器hosting(当然也支持这种方式)

    2. 只在根目录有1个hg目录用于代码管理,其他目录都很干净。这点是svn的最大诟病,尤其是这些目录都保存有文件的修改base,所以对于一些很大的二进制文件,一般目录大小会是常规的2倍

     安装好后,直接选一个空目录建立一个版本库,直接在版本库里就可以添加文件并commit,这点跟svn就不一样了,还得分清楚客户端和服务器。

     如果需要从原来的svn库导入到Hg的库,只需要用convert指令就可以。但是实际操作中,发现好像这个过程并不需要密码……

     有一个叫hgsubversion的库,可以从svn中将代码pull过来,也可以push回去。但是在我的VisualSVN Server中pull时,反复提示输入帐号及密码,实在不清楚是为什么。

     总结:公司做项目,用svn比较适合,毕竟Hg的分布式代码管理不是那么多人都会用而且有这个习惯,svn能统一习惯,再者,公司都是局域网,速度不成问题。如果是和几个朋友一起写代码,交流只能靠网络,而且网速不是很理想时,Hg就是很好的选择

posted @ 2010-01-28 17:06 战魂小筑 阅读(2103) | 评论 (3)编辑 收藏

前段时间曾经碰到过RT纹理绘制出来时需要透明的问题。当时也Google了一下,但是很少有人提起过这个问题。昨天看剑孤寒的空间的Galaxy2D引擎中使用RT的透明绘制,文章在这里。发现这个特性居然需要显卡支持,马上查过DX9SDK文档,发现这样一篇文章,已经告诉我们怎么做了:

Render Target Alpha (Direct3D 9)

The frame buffer blender can now blend alpha channels independent from color-channel blending on render targets. This control is enabled with a new render state, D3DRS_SEPARATEALPHABLENDENABLE.

When D3DRS_SEPARATEALPHABLENDENABLE is set to FALSE (which is the default condition), the render-target blending factors and operations applied to alpha are the same as those defined for blending color channels. A driver needs to set the D3DPMISCCAPS_SEPARATEALPHABLEND cap to indicate that it can support render-target alpha blending. Be sure to enable D3DRS_ALPHABLEND to tell the pipeline that alpha blending is needed.

To control the factors in the alpha channel of the render-target blenders, two new render states are defined as follows:

D3DRS_SRCBLENDALPHA 
D3DRS_DESTBLENDALPHA 

Like the D3DRS_SRCBLEND and D3DRS_DESTBLEND, these can be set to one of the values in the D3DBLEND enumeration. The source and destination blend settings can be combined in several ways, depending on the settings in the SrcBlendCaps and DestBlendCaps members of D3DCAPS9.

The alpha blending is done as follows:

renderTargetAlpha = (alphain* srcBlendOp) BlendOp (alphart* destBlendOp) 

Where:

  • alphain is the input alpha value.
  • srcBlendOp is one of the blend factors in D3DBLEND.
  • BlendOp is one of the blend factors in D3DBLENDOP.
  • alphart is the render-target alpha value.
  • destBlendOp is one of the blend factors in D3DBLEND.
  • renderTargetAlpha is the final blended alpha value.

 

翻译如下:

   使用 D3DRS_SEPARATEALPHABLENDENABLE渲染状态可以让Frame Buffer 混合器将RT中的Alpha通道与颜色通道分开混合。

当D3DRS_SEPARATEALPHABLENDENABLE 设置为 FALSE(默认),RT渲染参数和操作会跟颜色通道一样被应用到Alpha通道。 这项特性需要显卡支持D3DPMISCCAPS_SEPARATEALPHABLEND 特性。记住,在之前设置D3DRS_ALPHABLEND以便打开Alpha混合。

   RT混合器的Alpha通道混合因子渲染状态如下:

D3DRS_SRCBLENDALPHA

D3DRS_DESTBLENDALPHA

其被定义在D3DBLEND枚举中,D3DRS_SRCBLEND 和D3DRS_DESTBLEND也是这样定义的。来源色与目标颜色将会有很多组合方式,主要依赖于D3DCAPS9中的SrcBlendCaps 和DestBlendCaps

Alpha混合公式如下:

renderTargetAlpha = (alphain* srcBlendOp) BlendOp (alphart* destBlendOp)

其中:

    alphain为输入alpha值

    srcBlendOp是D3DBLEND中的一个混合因子

    BlendOp是 D3DBLENDOP中的一个混合因子

    alphart是RT的alpha值

    destBlendOp是D3DBLEND中的一个混合因子

    renderTargetAlpha是最后混合后的alpha值

posted @ 2010-01-20 16:56 战魂小筑 阅读(2789) | 评论 (1)编辑 收藏

早上打开GR阅读新闻时还是中文版,刚才刷新了下,全变英文版了,有图有真相

image

而昨天下午,Google.cn又解除了对中国的搜索屏蔽,不得不觉得Google太杯具了。

最近在看李开复自传,还没看到谷歌那章,但已经在微软那节感觉中国GOV实在不是什么好BIRD。

那天问女友为什么一直喜欢用Chrome和谷歌,她说,Google的logo好看,百度的太丑。我想,也许Google不仅仅是logo好看,即便在中国被过滤,但其不作恶的作风,还有给用户提供最新的技术(Google Earth,Google 地图搜索中的面部识别,Chrome)上,我永远支持Google

posted @ 2010-01-15 11:16 战魂小筑 阅读(2930) | 评论 (11)编辑 收藏

前几天需要做一个鼠标点击判定,具体是判断一个点是否在某个凸四边形中。

最简单的方法莫过于判断鼠标点是否在2个三角形中。但是很多判定方法都是有问题的,比如说

 

copy自IndieLib

bool Triangle2D::Inside2( const Vector2& p )
{
    Vector2 v0 = mP3 - mP1;
    Vector2 v1 = mP2 - mP1;
    Vector2 v2 = p - mP1; 

    // Compute dot products
    float dot00 =  Vector2::DotProduct( v0, v0 );
    float dot01 =  Vector2::DotProduct( v0, v1 );
    float dot02 =  Vector2::DotProduct( v0, v2 );
    float dot11 =  Vector2::DotProduct( v1, v1 );
    float dot12 =  Vector2::DotProduct( v1, v2 ); 

    // Compute barycentric coordinates
    float invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
    float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
    float v = (dot00 * dot12 - dot01 * dot02) * invDenom; 

    // Check if point is in triangle
    return (u > 0) && (v > 0) && (u + v < 1);
} 

  

Google出的某人代码

float Triangle2D::CrossProduct3(const Vector2& p1,const Vector2& p2, const Vector2& p0 )
{
    return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
} 

bool Triangle2D::Inside( const Vector2& p )
{
    return (CrossProduct3(mP1,p,mP2)*CrossProduct3(mP3,p,mP2)<0) &&
           (CrossProduct3(mP2,p,mP1)*CrossProduct3(mP3,p,mP1)<0) &&
           (CrossProduct3(mP1,p,mP3)*CrossProduct3(mP2,p,mP3)<0);
} 

 

这2个方法都有缺陷,当点在三角形边上时,就无法得出。当用在一个正方形判断时,正方形中心点就判定为没有在其内部,显然是一个错误。

 

之后,又Google出某几个大侠的算法和思想,考虑了下,判定点与四边形重心点的线段是否与四边形4条边相交,相交时,其在四边形外部,反之亦然。

bool Quadrangle::Inside2( const Vector2& p )
{
    Vector2 c = Segement2D::GetCrossPoint( mP1, mP3, mP2, mP4 ); 

    return !(Segement2D::Intersect( mP1, mP2, c, p) || 
           Segement2D::Intersect( mP2, mP3, c, p) ||
           Segement2D::Intersect( mP3, mP4, c, p) ||
           Segement2D::Intersect( mP4, mP1, c, p) );
} 

bool Segement2D::Intersect( const Vector2& p1, const Vector2& p2,const Vector2& p3, const Vector2& p4 )
{
    float gradab, gradcd, ycptab, ycptcd, interceptX, intercepty; 

    // In order to avoid divisions by zero
    //if (mP1.y == mP2.y)
    //    mP2.y += 0.0001f; 

    //if (mP1.x == mP2.x)
    //    mP2.x += 0.0001f; 

    //if (seg.mP1.y == seg.mP2.y)
    //    seg.mP2.y += 0.0001f; 

    //if (seg.mP1.x == seg.mP2.x)
    //    seg.mP2.x += 0.0001f; 

    // Calculates the intersection between the two lines
    gradab = (p1.y - p2.y) / (p1.x - p2.x);
    gradcd = (p3.y - p4.y) / (p3.x - p4.x); 

    ycptab = p1.y - p1.x * gradab;
    ycptcd = p3.y - p3.x * gradcd;
    interceptX = (ycptab - ycptcd) / (gradcd - gradab);
    intercepty = (ycptab - (gradab * ycptcd) / gradcd) / (1 - gradab / gradcd); 

    // Checking in the intersection is inside the segment
    if (!((interceptX >= p1.x && interceptX <= p2.x) || (interceptX >= p2.x && interceptX <= p1.x)))
        return 0; 

    if (!((intercepty >= p1.y && intercepty <= p2.y) || (intercepty >= p2.y && intercepty <= p1.y)))
        return 0; 

    if (!((interceptX >= p3.x && interceptX <= p4.x) || (interceptX >= p4.x && interceptX <= p3.x)))
        return 0; 

    if (!((intercepty >= p3.y && intercepty <= p4.y) || (intercepty >= p4.y && intercepty <= p3.y)))
        return 0; 

    return 1;
} 

Vector2 Segement2D::GetCrossPoint(const Vector2& p1, const Vector2& p2, const Vector2& q1, const Vector2& q2)
{
    //必须相交求出的才是线段的交点,但是下面的程序段是通用的 

    /*根据两点式化为标准式,进而求线性方程组*/
    Vector2 crossPoint;
    //求x坐标
    float tempLeft = (q2.x - q1.x) * (p1.y - p2.y) - (p2.x - p1.x) * (q1.y - q2.y);
    float tempRight = (p1.y - q1.y) * (p2.x - p1.x) * (q2.x - q1.x) + q1.x * (q2.y - q1.y) * (p2.x - p1.x) - p1.x * (p2.y - p1.y) * (q2.x - q1.x);
    crossPoint.x = tempRight / tempLeft;
    //求y坐标
    tempLeft = (p1.x - p2.x) * (q2.y - q1.y) - (p2.y - p1.y) * (q1.x - q2.x);
    tempRight = p2.y * (p1.x - p2.x) * (q2.y - q1.y) + (q2.x- p2.x) * (q2.y - q1.y) * (p1.y - p2.y) - q2.y * (q1.x - q2.x) * (p2.y - p1.y);
    crossPoint.y = tempRight / tempLeft; 

    return crossPoint;
}

这个算法效率并不是很高,但对于设计器来说无所谓了,如果有好的准确算法,可以讨论

posted @ 2010-01-08 10:29 战魂小筑 阅读(2705) | 评论 (6)编辑 收藏

前几天需要做一个鼠标点击判定,具体是判断一个点是否在某个凸四边形中。

最简单的方法莫过于判断鼠标点是否在2个三角形中。但是很多判定方法都是有问题的,比如说

 

copy自IndieLib

bool Triangle2D::Inside2( const Vector2& p )
{
    Vector2 v0 = mP3 - mP1;
    Vector2 v1 = mP2 - mP1;
    Vector2 v2 = p - mP1; 

    // Compute dot products
    float dot00 =  Vector2::DotProduct( v0, v0 );
    float dot01 =  Vector2::DotProduct( v0, v1 );
    float dot02 =  Vector2::DotProduct( v0, v2 );
    float dot11 =  Vector2::DotProduct( v1, v1 );
    float dot12 =  Vector2::DotProduct( v1, v2 ); 

    // Compute barycentric coordinates
    float invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
    float u = (dot11 * dot02 - dot01 * dot12) * invDenom;
    float v = (dot00 * dot12 - dot01 * dot02) * invDenom; 

    // Check if point is in triangle
    return (u > 0) && (v > 0) && (u + v < 1);
} 

  

Google出的某人代码

float Triangle2D::CrossProduct3(const Vector2& p1,const Vector2& p2, const Vector2& p0 )
{
    return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
} 

bool Triangle2D::Inside( const Vector2& p )
{
    return (CrossProduct3(mP1,p,mP2)*CrossProduct3(mP3,p,mP2)<0) &&
           (CrossProduct3(mP2,p,mP1)*CrossProduct3(mP3,p,mP1)<0) &&
           (CrossProduct3(mP1,p,mP3)*CrossProduct3(mP2,p,mP3)<0);
} 

 

这2个方法都有缺陷,当点在三角形边上时,就无法得出。当用在一个正方形判断时,正方形中心点就判定为没有在其内部,显然是一个错误。

 

之后,又Google出某几个大侠的算法和思想,考虑了下,判定点与四边形重心点的线段是否与四边形4条边相交,相交时,其在四边形外部,反之亦然。

bool Quadrangle::Inside2( const Vector2& p )
{
    Vector2 c = Segement2D::GetCrossPoint( mP1, mP3, mP2, mP4 ); 

    return !(Segement2D::Intersect( mP1, mP2, c, p) || 
           Segement2D::Intersect( mP2, mP3, c, p) ||
           Segement2D::Intersect( mP3, mP4, c, p) ||
           Segement2D::Intersect( mP4, mP1, c, p) );
} 

bool Segement2D::Intersect( const Vector2& p1, const Vector2& p2,const Vector2& p3, const Vector2& p4 )
{
    float gradab, gradcd, ycptab, ycptcd, interceptX, intercepty; 

    // In order to avoid divisions by zero
    //if (mP1.y == mP2.y)
    //    mP2.y += 0.0001f; 

    //if (mP1.x == mP2.x)
    //    mP2.x += 0.0001f; 

    //if (seg.mP1.y == seg.mP2.y)
    //    seg.mP2.y += 0.0001f; 

    //if (seg.mP1.x == seg.mP2.x)
    //    seg.mP2.x += 0.0001f; 

    // Calculates the intersection between the two lines
    gradab = (p1.y - p2.y) / (p1.x - p2.x);
    gradcd = (p3.y - p4.y) / (p3.x - p4.x); 

    ycptab = p1.y - p1.x * gradab;
    ycptcd = p3.y - p3.x * gradcd;
    interceptX = (ycptab - ycptcd) / (gradcd - gradab);
    intercepty = (ycptab - (gradab * ycptcd) / gradcd) / (1 - gradab / gradcd); 

    // Checking in the intersection is inside the segment
    if (!((interceptX >= p1.x && interceptX <= p2.x) || (interceptX >= p2.x && interceptX <= p1.x)))
        return 0; 

    if (!((intercepty >= p1.y && intercepty <= p2.y) || (intercepty >= p2.y && intercepty <= p1.y)))
        return 0; 

    if (!((interceptX >= p3.x && interceptX <= p4.x) || (interceptX >= p4.x && interceptX <= p3.x)))
        return 0; 

    if (!((intercepty >= p3.y && intercepty <= p4.y) || (intercepty >= p4.y && intercepty <= p3.y)))
        return 0; 

    return 1;
} 

Vector2 Segement2D::GetCrossPoint(const Vector2& p1, const Vector2& p2, const Vector2& q1, const Vector2& q2)
{
    //必须相交求出的才是线段的交点,但是下面的程序段是通用的 

    /*根据两点式化为标准式,进而求线性方程组*/
    Vector2 crossPoint;
    //求x坐标
    float tempLeft = (q2.x - q1.x) * (p1.y - p2.y) - (p2.x - p1.x) * (q1.y - q2.y);
    float tempRight = (p1.y - q1.y) * (p2.x - p1.x) * (q2.x - q1.x) + q1.x * (q2.y - q1.y) * (p2.x - p1.x) - p1.x * (p2.y - p1.y) * (q2.x - q1.x);
    crossPoint.x = tempRight / tempLeft;
    //求y坐标
    tempLeft = (p1.x - p2.x) * (q2.y - q1.y) - (p2.y - p1.y) * (q1.x - q2.x);
    tempRight = p2.y * (p1.x - p2.x) * (q2.y - q1.y) + (q2.x- p2.x) * (q2.y - q1.y) * (p1.y - p2.y) - q2.y * (q1.x - q2.x) * (p2.y - p1.y);
    crossPoint.y = tempRight / tempLeft; 

    return crossPoint;
}

这个算法效率并不是很高,但对于设计器来说无所谓了,如果有好的准确算法,可以讨论

posted @ 2010-01-08 10:27 战魂小筑 阅读(620) | 评论 (0)编辑 收藏

     准备为自己的2D游戏动画系统选择一套给外部使用的接口方式,有如下几种选择:

 

1. 纯C API方式导出,类似于Windows API方式

优点:简洁,可以供C/C#乃至其他可以导入C DLL的语言使用

缺点:C++的系统转化为C会遇到很多性能以及架构的折损,对于对象控制类功能导出比较多的系统,这种方式简直是噩梦

 

2. C++方式导出,宏方式实现RTTI以及C++反射系统

优点:强大,易用。

缺点:对象生命周期不容易控制,反射系统设计比较费时,可能暴露很多类的细节,只能给C++使用

 

3. COM方式,类DirectX的接口方式

优点:商业项目已经证明这种接口方式的强大地方。无论是多个对象的类功能导出,还是COM接口重载都非常方便。而且,可以极为容易的让.NET访问。XBOX SDK里的XUI就是采用这种方式,而且还设计了一套C API搭配的强大C++反射系统,支持动态类创建,RTTI,动态类型转换等等。

缺点:需要系统注册,不能跨平台。COM标准学习起来需要时间

 

4. 脚本绑定

优点:保持你的系统对外C/C++接口的干净,简洁。通过脚本类绑定,可以很快的将C++功能注册到脚本中。

缺点:脚本如果没有调试器,将会让开发中碰到的棘手问题,甚至于在后期维护系统以及系统架构大变动变得异常复杂。脚本的性能决定了不能让其做实时处理,例如:渲染

 

最终选择下来,由于有自己的界面系统Motion使用lua的函数绑定的前例,因此还是决定选择一款脚本语言来做系统的对外接口,这里有几个选择:

1. Lua

  可以说最好的游戏系统脚本语言。稳定,高效,bug几乎没有(即便有,普通开发者也是很难察觉的)。配上LuaPlus的强大C++绑定系统,你的系统开发效率可以提高很多。这里推荐notepad++来做lua开发,稍微配置下,弹出提示给你感觉在使用Visual Studio,:)

   同时需要指出的是,lua的类功能确实比较弱。虽然可以用metatable方式来模拟。但逼近跟native class支持还差很远,实际开发中,你能体会出class中的权限控制(private,protected)有多么重要。

2. Python

   诚然,这是个最OO的脚本。但是对于游戏,它太慢了。虽然本人只是用python写过一些build系统,但从很多朋友反应的情况来看,Python嵌入游戏系统,确实太慢。用Stackless Python? 用第三方开源产品,还是认准品牌,呵呵,这里如果有用过的同学,欢迎提供感受。

3. C#

   语言和系统都很美,但是想嵌入游戏,还是很痛的。.net平台可惜就在于,到现在为止,可能还不是所有机器默认安装有.net平台。如果你的游戏是C++写成,但却要安装.net以便你的脚本能运行,这很奇怪吧?所以,如果要用C#,还不如不用脚本,全盘.net就好了。Managed DX? 那东西几年前就被MS打入冷宫了。XNA? 那东西只是一个玩具,别摸。

4.Squirrel松鼠脚本

   这是个好东西。类lua的语法,C/C++/Java的语言结构,纯正的native class外加OO支持。拥有开源的C++类绑定系统。最爽的是,SQDEV支持日食(Eclipse)下的远程调试,开发环境还支持动态语法检查。oh,my god。还说不定哪天这脚本被MS招安,跟IronPython一样弄个.net绑定。

 

posted @ 2009-12-31 11:42 战魂小筑 阅读(2188) | 评论 (2)编辑 收藏

中文社区:http://silverlight.cn/

微软官方中文MSDN:http://msdn.microsoft.com/zh-cn/library/cc838158(VS.95).aspx

微软官方英文MSDN例子:http://samples.msdn.microsoft.com/Silverlight/SampleBrowser/index.htm#/?sref=HomePage

posted @ 2009-12-30 16:45 战魂小筑 阅读(1398) | 评论 (0)编辑 收藏

仅列出标题
共26页: First 16 17 18 19 20 21 22 23 24 Last